whatsapp_sdk 0.7.3 → 0.9.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/.github/workflows/ruby.yml +38 -233
- data/.gitignore +2 -0
- data/.rubocop.yml +7 -1
- data/CHANGELOG.md +9 -0
- data/Gemfile +0 -1
- data/Gemfile.lock +1 -4
- data/README.md +118 -1
- data/example.rb +89 -11
- data/lib/whatsapp_sdk/api/client.rb +10 -5
- data/lib/whatsapp_sdk/api/medias.rb +31 -3
- data/lib/whatsapp_sdk/api/messages.rb +50 -6
- data/lib/whatsapp_sdk/api/request.rb +2 -2
- data/lib/whatsapp_sdk/resource/error.rb +39 -0
- data/lib/whatsapp_sdk/resource/interactive.rb +89 -0
- data/lib/whatsapp_sdk/resource/interactive_action.rb +141 -0
- data/lib/whatsapp_sdk/resource/interactive_action_reply_button.rb +88 -0
- data/lib/whatsapp_sdk/resource/interactive_action_section.rb +72 -0
- data/lib/whatsapp_sdk/resource/interactive_action_section_row.rb +93 -0
- data/lib/whatsapp_sdk/resource/interactive_body.rb +48 -0
- data/lib/whatsapp_sdk/resource/interactive_footer.rb +48 -0
- data/lib/whatsapp_sdk/resource/interactive_header.rb +120 -0
- data/lib/whatsapp_sdk/resource/parameter_object.rb +5 -18
- data/lib/whatsapp_sdk/version.rb +1 -1
- data/sorbet/rbi/annotations/mocha.rbi +2 -2
- data/whatsapp_sdk.gemspec +0 -1
- metadata +11 -16
@@ -3,7 +3,6 @@
|
|
3
3
|
|
4
4
|
require "faraday"
|
5
5
|
require "faraday/multipart"
|
6
|
-
require "oj"
|
7
6
|
|
8
7
|
module WhatsappSdk
|
9
8
|
module Api
|
@@ -31,21 +30,27 @@ module WhatsappSdk
|
|
31
30
|
|
32
31
|
response = faraday_request.public_send(http_method, endpoint, request_params(params, headers), headers)
|
33
32
|
|
34
|
-
|
33
|
+
return nil if response.body == ""
|
34
|
+
|
35
|
+
JSON.parse(response.body)
|
35
36
|
end
|
36
37
|
|
37
|
-
sig
|
38
|
-
|
38
|
+
sig do
|
39
|
+
params(url: String, content_header: String, file_path: T.nilable(String))
|
40
|
+
.returns(Net::HTTPResponse)
|
41
|
+
end
|
42
|
+
def download_file(url:, content_header:, file_path: nil)
|
39
43
|
uri = URI.parse(url)
|
40
44
|
request = Net::HTTP::Get.new(uri)
|
41
45
|
request["Authorization"] = "Bearer #{@access_token}"
|
46
|
+
request.content_type = content_header
|
42
47
|
req_options = { use_ssl: uri.scheme == "https" }
|
43
48
|
|
44
49
|
response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
|
45
50
|
http.request(request)
|
46
51
|
end
|
47
52
|
|
48
|
-
File.write(
|
53
|
+
File.write(file_path, response.body, mode: 'wb') if response.code == "200" && file_path
|
49
54
|
|
50
55
|
response
|
51
56
|
end
|
@@ -25,6 +25,21 @@ module WhatsappSdk
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
+
class InvalidMediaTypeError < StandardError
|
29
|
+
extend T::Sig
|
30
|
+
|
31
|
+
sig { returns(String) }
|
32
|
+
attr_reader :media_type
|
33
|
+
|
34
|
+
sig { params(media_type: String).void }
|
35
|
+
def initialize(_media_type)
|
36
|
+
@file_path = file_path
|
37
|
+
message = "Invalid Media Type. See the supported types" \
|
38
|
+
"see the official documentation https://developers.facebook.com/docs/whatsapp/cloud-api/reference/media#supported-media-types."
|
39
|
+
super(message)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
28
43
|
# Get Media by ID.
|
29
44
|
#
|
30
45
|
# @param media_id [String] Media Id.
|
@@ -46,11 +61,16 @@ module WhatsappSdk
|
|
46
61
|
#
|
47
62
|
# @param url URL.
|
48
63
|
# @param file_path [String] The file_path to download the media e.g. "tmp/downloaded_image.png".
|
64
|
+
# @param media_type [String] The media type e.g. "audio/mp4". See the supported types in the official
|
65
|
+
# documentation https://developers.facebook.com/docs/whatsapp/cloud-api/reference/media#supported-media-types.
|
49
66
|
# @return [WhatsappSdk::Api::Response] Response object.
|
50
|
-
sig { params(url: String, file_path: String).returns(WhatsappSdk::Api::Response) }
|
51
|
-
def download(url:, file_path:)
|
52
|
-
|
67
|
+
sig { params(url: String, file_path: String, media_type: String).returns(WhatsappSdk::Api::Response) }
|
68
|
+
def download(url:, file_path:, media_type:)
|
69
|
+
return InvalidMediaTypeError(media_type) if media_type && !valid_content_header?(media_type)
|
70
|
+
|
71
|
+
content_header = media_type
|
53
72
|
|
73
|
+
response = download_file(url: url, file_path: file_path, content_header: content_header)
|
54
74
|
response = if response.code.to_i == 200
|
55
75
|
{ "success" => true }
|
56
76
|
else
|
@@ -105,6 +125,14 @@ module WhatsappSdk
|
|
105
125
|
data_class_type: WhatsappSdk::Api::Responses::SuccessResponse
|
106
126
|
)
|
107
127
|
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def valid_content_header?(_media_type)
|
132
|
+
# TODO: Add validations for media types. See available types in the official documentation
|
133
|
+
# https://developers.facebook.com/docs/whatsapp/cloud-api/reference/media#supported-media-types.
|
134
|
+
true
|
135
|
+
end
|
108
136
|
end
|
109
137
|
end
|
110
138
|
end
|
@@ -363,13 +363,57 @@ module WhatsappSdk
|
|
363
363
|
# # TODO: https://developers.facebook.com/docs/whatsapp_sdk/cloud-api/reference/messages#contacts-object
|
364
364
|
# end
|
365
365
|
|
366
|
-
#
|
367
|
-
#
|
368
|
-
#
|
366
|
+
# Send interactive reply buttons.
|
367
|
+
# https://developers.facebook.com/docs/whatsapp/guides/interactive-messages#reply-buttons
|
368
|
+
# You can either send interactive object or as JSON.
|
369
|
+
#
|
370
|
+
# @param sender_id [Integer] Sender' phone number.
|
371
|
+
# @param recipient_number [Integer] Recipient' Phone number.
|
372
|
+
# @param interactive [Interactive] Interactive.
|
373
|
+
# @param interactive_json [Json] The interactive object as a Json.
|
374
|
+
# If you pass interactive_json, you can't pass interactive.
|
375
|
+
# @param message_id [String] The id of the message to reply to.
|
376
|
+
# @return [WhatsappSdk::Api::Response] Response object.
|
377
|
+
sig do
|
378
|
+
params(
|
379
|
+
sender_id: Integer, recipient_number: Integer,
|
380
|
+
interactive: T.nilable(WhatsappSdk::Resource::Interactive),
|
381
|
+
interactive_json: T.nilable(T::Hash[T.untyped, T.untyped]), message_id: T.nilable(String)
|
382
|
+
).returns(WhatsappSdk::Api::Response)
|
383
|
+
end
|
384
|
+
def send_interactive_message(
|
385
|
+
sender_id:, recipient_number:, interactive: nil, interactive_json: nil, message_id: nil
|
386
|
+
)
|
387
|
+
raise MissingArgumentError, "interactive or interactive_json is required" if !interactive && !interactive_json
|
369
388
|
|
370
|
-
|
371
|
-
|
372
|
-
|
389
|
+
params = {
|
390
|
+
messaging_product: "whatsapp",
|
391
|
+
to: recipient_number,
|
392
|
+
recipient_type: "individual",
|
393
|
+
type: "interactive"
|
394
|
+
}
|
395
|
+
|
396
|
+
params[:interactive] = if interactive.nil?
|
397
|
+
interactive_json
|
398
|
+
else
|
399
|
+
interactive.to_json
|
400
|
+
end
|
401
|
+
params[:context] = { message_id: message_id } if message_id
|
402
|
+
|
403
|
+
response = send_request(
|
404
|
+
endpoint: endpoint(sender_id),
|
405
|
+
params: params,
|
406
|
+
headers: DEFAULT_HEADERS
|
407
|
+
)
|
408
|
+
|
409
|
+
WhatsappSdk::Api::Response.new(
|
410
|
+
response: response,
|
411
|
+
data_class_type: WhatsappSdk::Api::Responses::MessageDataResponse
|
412
|
+
)
|
413
|
+
end
|
414
|
+
|
415
|
+
alias send_interactive_reply_buttons send_interactive_message
|
416
|
+
alias send_interactive_list_messages send_interactive_message
|
373
417
|
|
374
418
|
# Mark a message as read.
|
375
419
|
#
|
@@ -10,8 +10,8 @@ module WhatsappSdk
|
|
10
10
|
@client = client
|
11
11
|
end
|
12
12
|
|
13
|
-
def download_file(url
|
14
|
-
@client.download_file(url,
|
13
|
+
def download_file(url:, content_header:, file_path: nil)
|
14
|
+
@client.download_file(url: url, content_header: content_header, file_path: file_path)
|
15
15
|
end
|
16
16
|
|
17
17
|
def send_request(endpoint: nil, full_url: nil, http_method: "post", params: {}, headers: {})
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module WhatsappSdk
|
5
|
+
module Resource
|
6
|
+
module Error
|
7
|
+
class MissingValue < WhatsappSdk::Error
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { returns(String) }
|
11
|
+
attr_reader :field
|
12
|
+
|
13
|
+
sig { returns(String) }
|
14
|
+
attr_reader :message
|
15
|
+
|
16
|
+
sig { params(field: String, message: String).void }
|
17
|
+
def initialize(field, message)
|
18
|
+
@field = field
|
19
|
+
@message = message
|
20
|
+
super(message)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class InvalidField < MissingValue; end
|
25
|
+
|
26
|
+
class InvalidInteractiveBody < WhatsappSdk::Error; end
|
27
|
+
|
28
|
+
class InvalidInteractiveActionReplyButton < WhatsappSdk::Error; end
|
29
|
+
|
30
|
+
class InvalidInteractiveActionButton < WhatsappSdk::Error; end
|
31
|
+
|
32
|
+
class InvalidInteractiveActionSection < WhatsappSdk::Error; end
|
33
|
+
|
34
|
+
class InvalidInteractiveActionSectionRow < WhatsappSdk::Error; end
|
35
|
+
|
36
|
+
class InvalidInteractiveFooter < WhatsappSdk::Error; end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module WhatsappSdk
|
5
|
+
module Resource
|
6
|
+
class Interactive
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
class Type < T::Enum
|
10
|
+
extend T::Sig
|
11
|
+
|
12
|
+
enums do
|
13
|
+
ListMessage = new("list")
|
14
|
+
ReplyButton = new("button")
|
15
|
+
SingleProductMessage = new("product")
|
16
|
+
MultiProductMessage = new("product_list")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the Interactive type of message you want to send.
|
21
|
+
#
|
22
|
+
# @returns type [Type]. Supported Options are list, button, product and product_list.
|
23
|
+
sig { returns(Type) }
|
24
|
+
attr_accessor :type
|
25
|
+
|
26
|
+
# Returns the interactive header if present. Required for type product_list.
|
27
|
+
#
|
28
|
+
# @returns type [InteractiveHeader] It can be nil.
|
29
|
+
sig { returns(T.nilable(InteractiveHeader)) }
|
30
|
+
attr_accessor :header
|
31
|
+
|
32
|
+
# Returns the interactive body.
|
33
|
+
#
|
34
|
+
# @returns type [InteractiveBody] Valid option is of type text only.
|
35
|
+
sig { returns(InteractiveBody) }
|
36
|
+
attr_accessor :body
|
37
|
+
|
38
|
+
# Returns the interactive footer if present.
|
39
|
+
#
|
40
|
+
# @returns type [InteractiveFooter] Valid option is of type text only. It can be nil.
|
41
|
+
sig { returns(T.nilable(InteractiveFooter)) }
|
42
|
+
attr_accessor :footer
|
43
|
+
|
44
|
+
# Returns the interactive action.
|
45
|
+
#
|
46
|
+
# @returns type [InteractiveBody] Valid condition is buttons of length of 1, 2 or 3 if type is button.
|
47
|
+
sig { returns(InteractiveAction) }
|
48
|
+
attr_accessor :action
|
49
|
+
|
50
|
+
sig do
|
51
|
+
params(
|
52
|
+
type: Type, body: InteractiveBody, action: InteractiveAction,
|
53
|
+
header: T.nilable(InteractiveHeader), footer: T.nilable(InteractiveFooter)
|
54
|
+
).void
|
55
|
+
end
|
56
|
+
def initialize(type:, body:, action:, header: nil, footer: nil)
|
57
|
+
@type = type
|
58
|
+
@body = body
|
59
|
+
@action = action
|
60
|
+
@header = header
|
61
|
+
@footer = footer
|
62
|
+
validate
|
63
|
+
end
|
64
|
+
|
65
|
+
sig { returns(T::Hash[T.untyped, T.untyped]) }
|
66
|
+
def to_json
|
67
|
+
json = { type: type.serialize }
|
68
|
+
json[:header] = header.to_json if header
|
69
|
+
json[:body] = body.to_json
|
70
|
+
json[:footer] = footer.to_json if footer
|
71
|
+
json[:action] = action.to_json
|
72
|
+
|
73
|
+
json
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
sig { void }
|
79
|
+
def validate
|
80
|
+
validate_action
|
81
|
+
end
|
82
|
+
|
83
|
+
sig { void }
|
84
|
+
def validate_action
|
85
|
+
action.validate
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module WhatsappSdk
|
5
|
+
module Resource
|
6
|
+
class InteractiveAction
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
class Type < T::Enum
|
10
|
+
extend T::Sig
|
11
|
+
|
12
|
+
enums do
|
13
|
+
ListMessage = new("list_message")
|
14
|
+
ReplyButton = new("reply_button")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns the type of interactive action you want to send.
|
19
|
+
#
|
20
|
+
# @returns type [Type]. Supported Options are list_message and reply_button.
|
21
|
+
sig { returns(Type) }
|
22
|
+
attr_accessor :type
|
23
|
+
|
24
|
+
# Returns the buttons of the Action. For reply_button type, it's required.
|
25
|
+
#
|
26
|
+
# @returns buttons [Array<InteractiveActionReplyButton>] .
|
27
|
+
sig { returns(T::Array[InteractiveActionReplyButton]) }
|
28
|
+
attr_accessor :buttons
|
29
|
+
|
30
|
+
# Returns the button of the Action. For list_message type, it's required.
|
31
|
+
#
|
32
|
+
# @returns button [String] .
|
33
|
+
sig { returns(String) }
|
34
|
+
attr_accessor :button
|
35
|
+
|
36
|
+
# Returns the sections of the Action. For list_message type, it's required.
|
37
|
+
#
|
38
|
+
# @returns sections [Array<InteractiveActionSection>] .
|
39
|
+
sig { returns(T::Array[InteractiveActionSection]) }
|
40
|
+
attr_accessor :sections
|
41
|
+
|
42
|
+
# TODO: attr_accessor :catalog_id
|
43
|
+
# TODO: attr_accessor :product_retailer_id
|
44
|
+
|
45
|
+
sig { params(reply_button: InteractiveActionReplyButton).void }
|
46
|
+
def add_reply_button(reply_button)
|
47
|
+
@buttons << reply_button
|
48
|
+
end
|
49
|
+
|
50
|
+
sig { params(section: InteractiveActionSection).void }
|
51
|
+
def add_section(section)
|
52
|
+
@sections << section
|
53
|
+
end
|
54
|
+
|
55
|
+
REPLY_BUTTONS_MINIMUM = 1
|
56
|
+
REPLY_BUTTONS_MAXIMUM = 3
|
57
|
+
LIST_BUTTON_TITLE_MAXIMUM = 20
|
58
|
+
LIST_SECTIONS_MINIMUM = 1
|
59
|
+
LIST_SECTIONS_MAXIMUM = 10
|
60
|
+
|
61
|
+
sig do
|
62
|
+
params(
|
63
|
+
type: Type, buttons: T::Array[InteractiveActionReplyButton],
|
64
|
+
button: String, sections: T::Array[InteractiveActionSection]
|
65
|
+
).void
|
66
|
+
end
|
67
|
+
def initialize(type:, buttons: [], button: "", sections: [])
|
68
|
+
@type = type
|
69
|
+
@buttons = buttons
|
70
|
+
@button = button
|
71
|
+
@sections = sections
|
72
|
+
end
|
73
|
+
|
74
|
+
sig { returns(T::Hash[T.untyped, T.untyped]) }
|
75
|
+
def to_json
|
76
|
+
json = {}
|
77
|
+
case type.serialize
|
78
|
+
when "list_message"
|
79
|
+
json = { button: button, sections: sections.map(&:to_json) }
|
80
|
+
when "reply_button"
|
81
|
+
json = { buttons: buttons.map(&:to_json) }
|
82
|
+
end
|
83
|
+
|
84
|
+
json
|
85
|
+
end
|
86
|
+
|
87
|
+
def validate
|
88
|
+
validate_fields
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def validate_fields
|
94
|
+
case type.serialize
|
95
|
+
when "list_message"
|
96
|
+
validate_list_message
|
97
|
+
when "reply_button"
|
98
|
+
validate_reply_button
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def validate_list_message
|
103
|
+
button_length = button.length
|
104
|
+
sections_count = sections.length
|
105
|
+
unless button_length.positive?
|
106
|
+
raise WhatsappSdk::Resource::Error::InvalidInteractiveActionButton,
|
107
|
+
"Invalid button in action. Button label is required."
|
108
|
+
end
|
109
|
+
|
110
|
+
unless button_length <= LIST_BUTTON_TITLE_MAXIMUM
|
111
|
+
raise WhatsappSdk::Resource::Error::InvalidInteractiveActionButton,
|
112
|
+
"Invalid length #{button_length} for button. Maximum length: " \
|
113
|
+
"#{LIST_BUTTON_TITLE_MAXIMUM} characters."
|
114
|
+
end
|
115
|
+
|
116
|
+
unless (LIST_SECTIONS_MINIMUM..LIST_SECTIONS_MAXIMUM).cover?(sections_count)
|
117
|
+
raise WhatsappSdk::Resource::Error::InvalidInteractiveActionSection,
|
118
|
+
"Invalid length #{sections_count} for sections in action. It should be between " \
|
119
|
+
"#{LIST_SECTIONS_MINIMUM} and #{LIST_SECTIONS_MAXIMUM}."
|
120
|
+
end
|
121
|
+
|
122
|
+
sections.each(&:validate)
|
123
|
+
end
|
124
|
+
|
125
|
+
def validate_reply_button
|
126
|
+
buttons_count = buttons.length
|
127
|
+
unless (REPLY_BUTTONS_MINIMUM..REPLY_BUTTONS_MAXIMUM).cover?(buttons_count)
|
128
|
+
raise WhatsappSdk::Resource::Error::InvalidInteractiveActionReplyButton,
|
129
|
+
"Invalid length #{buttons_count} for buttons in action. It should be between " \
|
130
|
+
"#{REPLY_BUTTONS_MINIMUM} and #{REPLY_BUTTONS_MAXIMUM}."
|
131
|
+
end
|
132
|
+
|
133
|
+
button_ids = buttons.map(&:id)
|
134
|
+
return if button_ids.length.eql?(button_ids.uniq.length)
|
135
|
+
|
136
|
+
raise WhatsappSdk::Resource::Error::InvalidInteractiveActionReplyButton,
|
137
|
+
"Duplicate ids #{button_ids} for buttons in action. They should be unique."
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module WhatsappSdk
|
5
|
+
module Resource
|
6
|
+
class InteractiveActionReplyButton
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
class Type < T::Enum
|
10
|
+
extend T::Sig
|
11
|
+
|
12
|
+
enums do
|
13
|
+
Reply = new("reply")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns the ActionButton type of message you want to send.
|
18
|
+
#
|
19
|
+
# @returns type [String]. Supported Options are reply only.
|
20
|
+
sig { returns(Type) }
|
21
|
+
attr_accessor :type
|
22
|
+
|
23
|
+
# Returns the ActionButton title you want to send.
|
24
|
+
#
|
25
|
+
# @returns title [String]. The character limit is 20 characters.
|
26
|
+
sig { returns(String) }
|
27
|
+
attr_accessor :title
|
28
|
+
|
29
|
+
# Returns the ActionButton unique identifier you want to send.
|
30
|
+
# This ID is returned in the webhook when the button is clicked by the user.
|
31
|
+
#
|
32
|
+
# @returns id [String]. The character limit is 256 characters.
|
33
|
+
sig { returns(String) }
|
34
|
+
attr_accessor :id
|
35
|
+
|
36
|
+
ACTION_BUTTON_TITLE_MAXIMUM = 20
|
37
|
+
ACTION_BUTTON_ID_MAXIMUM = 256
|
38
|
+
|
39
|
+
sig { params(title: String, id: String).void }
|
40
|
+
def initialize(title:, id:)
|
41
|
+
@type = Type::Reply
|
42
|
+
@title = title
|
43
|
+
@id = id
|
44
|
+
validate
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_json
|
48
|
+
json = { type: type.serialize }
|
49
|
+
json[type.serialize.to_sym] = {
|
50
|
+
id: id,
|
51
|
+
title: title
|
52
|
+
}
|
53
|
+
|
54
|
+
json
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
sig { void }
|
60
|
+
def validate
|
61
|
+
validate_title
|
62
|
+
validate_id
|
63
|
+
end
|
64
|
+
|
65
|
+
sig { void }
|
66
|
+
def validate_title
|
67
|
+
title_length = title.length
|
68
|
+
return if title_length <= ACTION_BUTTON_TITLE_MAXIMUM
|
69
|
+
|
70
|
+
raise WhatsappSdk::Resource::Error::InvalidInteractiveActionReplyButton,
|
71
|
+
"Invalid length #{title_length} for title in button. " \
|
72
|
+
"Maximum length: #{ACTION_BUTTON_TITLE_MAXIMUM} characters."
|
73
|
+
end
|
74
|
+
|
75
|
+
sig { void }
|
76
|
+
def validate_id
|
77
|
+
id_unfrozen = id.dup
|
78
|
+
id_unfrozen.strip! # You cannot have leading or trailing spaces when setting the ID.
|
79
|
+
id = id_unfrozen.freeze
|
80
|
+
id_length = id.length
|
81
|
+
return if id_length <= ACTION_BUTTON_ID_MAXIMUM
|
82
|
+
|
83
|
+
raise WhatsappSdk::Resource::Error::InvalidInteractiveActionReplyButton,
|
84
|
+
"Invalid length #{id_length} for id in button. Maximum length: #{ACTION_BUTTON_ID_MAXIMUM} characters."
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module WhatsappSdk
|
5
|
+
module Resource
|
6
|
+
class InteractiveActionSection
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
# Returns the ActionSection title you want to send.
|
10
|
+
#
|
11
|
+
# @returns title [String]. The character limit is 24 characters.
|
12
|
+
sig { returns(String) }
|
13
|
+
attr_accessor :title
|
14
|
+
|
15
|
+
# Returns the ActionSection rows you want to send.
|
16
|
+
#
|
17
|
+
# @returns id [T::Array[InteractiveActionSectionRow]]. There must be at least one rows object.
|
18
|
+
sig { returns(T::Array[InteractiveActionSectionRow]) }
|
19
|
+
attr_accessor :rows
|
20
|
+
|
21
|
+
sig { params(row: InteractiveActionSectionRow).void }
|
22
|
+
def add_row(row)
|
23
|
+
@rows << row
|
24
|
+
end
|
25
|
+
|
26
|
+
ACTION_SECTION_TITLE_MAXIMUM = 24
|
27
|
+
ACTION_SECTION_ROWS_MAXIMUM = 10
|
28
|
+
|
29
|
+
sig { params(title: String, rows: T::Array[InteractiveActionSectionRow]).void }
|
30
|
+
def initialize(title:, rows: [])
|
31
|
+
@title = title
|
32
|
+
@rows = rows
|
33
|
+
validate(skip_rows: true)
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_json
|
37
|
+
{
|
38
|
+
title: title,
|
39
|
+
rows: rows.map(&:to_json)
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
sig { params(skip_rows: T.nilable(T::Boolean)).void }
|
44
|
+
def validate(skip_rows: false)
|
45
|
+
validate_title
|
46
|
+
validate_rows unless skip_rows
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
sig { void }
|
52
|
+
def validate_title
|
53
|
+
title_length = title.length
|
54
|
+
return if title_length <= ACTION_SECTION_TITLE_MAXIMUM
|
55
|
+
|
56
|
+
raise WhatsappSdk::Resource::Error::InvalidInteractiveActionSection,
|
57
|
+
"Invalid length #{title_length} for title in section. Maximum length: " \
|
58
|
+
"#{ACTION_SECTION_TITLE_MAXIMUM} characters."
|
59
|
+
end
|
60
|
+
|
61
|
+
sig { void }
|
62
|
+
def validate_rows
|
63
|
+
rows_length = rows.length
|
64
|
+
return if rows_length <= ACTION_SECTION_ROWS_MAXIMUM
|
65
|
+
|
66
|
+
raise WhatsappSdk::Resource::Error::InvalidInteractiveActionSection,
|
67
|
+
"Invalid number of rows #{rows_length} in section. Maximum count: " \
|
68
|
+
"#{ACTION_SECTION_ROWS_MAXIMUM}."
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module WhatsappSdk
|
5
|
+
module Resource
|
6
|
+
class InteractiveActionSectionRow
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
# Returns the ActionSection title you want to send.
|
10
|
+
#
|
11
|
+
# @returns title [String]. The character limit is 24 characters.
|
12
|
+
sig { returns(String) }
|
13
|
+
attr_accessor :title
|
14
|
+
|
15
|
+
# Returns the ActionSection description you want to send.
|
16
|
+
#
|
17
|
+
# @returns description [String]. The character limit is 72 characters if present.
|
18
|
+
sig { returns(String) }
|
19
|
+
attr_accessor :description
|
20
|
+
|
21
|
+
# Returns the ActionSection unique identifier you want to send.
|
22
|
+
# This ID is returned in the webhook when the section is selected by the user.
|
23
|
+
#
|
24
|
+
# @returns id [String]. The character limit is 256 characters.
|
25
|
+
sig { returns(String) }
|
26
|
+
attr_accessor :id
|
27
|
+
|
28
|
+
ACTION_SECTION_TITLE_MAXIMUM = 24
|
29
|
+
ACTION_SECTION_DESCRIPTION_MAXIMUM = 72
|
30
|
+
ACTION_SECTION_ID_MAXIMUM = 256
|
31
|
+
|
32
|
+
sig { params(title: String, id: String, description: T.nilable(String)).void }
|
33
|
+
def initialize(title:, id:, description: "")
|
34
|
+
@title = title
|
35
|
+
@id = id
|
36
|
+
@description = description
|
37
|
+
validate
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_json
|
41
|
+
json = {
|
42
|
+
id: id,
|
43
|
+
title: title
|
44
|
+
}
|
45
|
+
json[:description] = description if description.length.positive?
|
46
|
+
|
47
|
+
json
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
sig { void }
|
53
|
+
def validate
|
54
|
+
validate_title
|
55
|
+
validate_id
|
56
|
+
validate_description
|
57
|
+
end
|
58
|
+
|
59
|
+
sig { void }
|
60
|
+
def validate_title
|
61
|
+
title_length = title.length
|
62
|
+
return if title_length <= ACTION_SECTION_TITLE_MAXIMUM
|
63
|
+
|
64
|
+
raise WhatsappSdk::Resource::Error::InvalidInteractiveActionSectionRow,
|
65
|
+
"Invalid length #{title_length} for title in section row. "\
|
66
|
+
"Maximum length: #{ACTION_SECTION_TITLE_MAXIMUM} characters."
|
67
|
+
end
|
68
|
+
|
69
|
+
sig { void }
|
70
|
+
def validate_id
|
71
|
+
id_unfrozen = id.dup
|
72
|
+
id_unfrozen.strip! # You cannot have leading or trailing spaces when setting the ID.
|
73
|
+
id = id_unfrozen.freeze
|
74
|
+
id_length = id.length
|
75
|
+
return if id_length <= ACTION_SECTION_ID_MAXIMUM
|
76
|
+
|
77
|
+
raise WhatsappSdk::Resource::Error::InvalidInteractiveActionSectionRow,
|
78
|
+
"Invalid length #{id_length} for id in section row. Maximum length: "\
|
79
|
+
"#{ACTION_SECTION_ID_MAXIMUM} characters."
|
80
|
+
end
|
81
|
+
|
82
|
+
sig { void }
|
83
|
+
def validate_description
|
84
|
+
description_length = description.length
|
85
|
+
return if description_length <= ACTION_SECTION_DESCRIPTION_MAXIMUM
|
86
|
+
|
87
|
+
raise WhatsappSdk::Resource::Error::InvalidInteractiveActionSectionRow,
|
88
|
+
"Invalid length #{description_length} for description in section " \
|
89
|
+
"row. Maximum length: #{ACTION_SECTION_DESCRIPTION_MAXIMUM} characters."
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|