whatsapp_sdk 0.8.0 → 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/.gitignore +2 -0
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/example.rb +42 -8
- data/lib/whatsapp_sdk/api/client.rb +7 -3
- data/lib/whatsapp_sdk/api/medias.rb +31 -3
- data/lib/whatsapp_sdk/api/messages.rb +2 -2
- data/lib/whatsapp_sdk/api/request.rb +2 -2
- data/lib/whatsapp_sdk/resource/interactive_action.rb +38 -30
- data/lib/whatsapp_sdk/resource/interactive_action_section.rb +2 -5
- data/lib/whatsapp_sdk/resource/interactive_action_section_row.rb +11 -12
- data/lib/whatsapp_sdk/version.rb +1 -1
- data/sorbet/rbi/annotations/mocha.rbi +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5fb8ebc1170878767c0800ab380f6cca2cd853fe59ec7496921781ef85fde8fb
|
4
|
+
data.tar.gz: 67e312ba37cc9c8e612af0a5d0840c1b7933eb3dfcebb9641faee41194387d21
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 856af75ac80060b7f0c6e282fc64d396a102dc31c6cbd8bf09c15bcee55b265ee4e0e5ffbff7e3bab055659233cd4d2db087a6303598128d881ae935b5d45685
|
7
|
+
data.tar.gz: d709a2b39b758cb294a1cc682320bfa4be3a634d137561e01b79f66c3df042312a31e07925d774ab42e84b51e7597a1b20bb6ef1965503a84085cfb1827aca58
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
# Unreleased
|
2
2
|
|
3
|
+
# v 0.9.0
|
4
|
+
- Use binary mode to download files @ignacio-chiazzo [#88](https://github.com/ignacio-chiazzo/ruby_whatsapp_sdk/pull/87)
|
5
|
+
- Added support for downloading media by specifying the type @ignacio-chiazzo [#87](https://github.com/ignacio-chiazzo/ruby_whatsapp_sdk/pull/87)
|
6
|
+
|
3
7
|
# v 0.8.0
|
4
8
|
- Added Send interactive message @alienware [#82](https://github.com/ignacio-chiazzo/ruby_whatsapp_sdk/pull/82)
|
5
9
|
- Support JRuby @ignacio-chiazzo [#83](https://github.com/ignacio-chiazzo/ruby_whatsapp_sdk/pull/83)
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -183,7 +183,7 @@ media = medias_api.media(media_id: MEDIA_ID)
|
|
183
183
|
|
184
184
|
Download media
|
185
185
|
```ruby
|
186
|
-
medias_api.download(url: MEDIA_URL, file_path: 'tmp/downloaded_whatsapp.png')
|
186
|
+
medias_api.download(url: MEDIA_URL, file_path: 'tmp/downloaded_whatsapp.png', media_type: "image/png")
|
187
187
|
```
|
188
188
|
|
189
189
|
Delete a media
|
data/example.rb
CHANGED
@@ -22,11 +22,12 @@ require "pry-nav"
|
|
22
22
|
|
23
23
|
################# UPDATE CONSTANTS #################
|
24
24
|
|
25
|
-
ACCESS_TOKEN = "
|
26
|
-
SENDER_ID =
|
27
|
-
RECIPIENT_NUMBER =
|
28
|
-
BUSINESS_ID =
|
25
|
+
ACCESS_TOKEN = "EAAHlmy2rChwBAGELLYnNnJfhKOPuSuaX5cRrfYA65RLY2NlEsMQ4x4tO3fz2imwrhmyx2pvKnC07tm0sWRzFHEko7CtXoZBTSb3lrBrKlx86eDvtdZBm2P2ewEJPbotfMYhTYwLsfMyRdQqgNAmc0wij1hMTHOusZALovPKHsme3RvAo1Ag1wqZA3qrPB2WhZChhKWPOkVQZDZD"
|
26
|
+
SENDER_ID = 100219219709628
|
27
|
+
RECIPIENT_NUMBER = 15550429560
|
28
|
+
BUSINESS_ID = 102_261_539_298_487
|
29
29
|
IMAGE_LINK = "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png"
|
30
|
+
AUDIO_LINK = "https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2951440611667284&ext=1681491953&hash=ATsLUNWiMmGDndn5YlpWQFHm5CUXca0gdahSJTCp5XjgTQ"
|
30
31
|
|
31
32
|
if ACCESS_TOKEN == "<TODO replace>"
|
32
33
|
puts "\n\n**** Please update the ACCESS_TOKEN constant in this file. ****\n\n"
|
@@ -53,6 +54,23 @@ messages_api = WhatsappSdk::Api::Messages.new
|
|
53
54
|
phone_numbers_api = WhatsappSdk::Api::PhoneNumbers.new
|
54
55
|
business_profile_api = WhatsappSdk::Api::BusinessProfile.new
|
55
56
|
|
57
|
+
binding.pry
|
58
|
+
# upload an audio
|
59
|
+
uploaded_media = medias_api.upload(sender_id: SENDER_ID, file_path: "tmp/downloaded_audio.ogg", type: "audio/ogg")
|
60
|
+
media_id = uploaded_media.data&.id
|
61
|
+
puts "Uploaded media id: #{media_id}"
|
62
|
+
|
63
|
+
# get a media audio
|
64
|
+
media = medias_api.media(media_id: media_id).data
|
65
|
+
puts "Media info: #{media.raw_data_response}"
|
66
|
+
|
67
|
+
# get a media audio
|
68
|
+
audio_link = media.url
|
69
|
+
download_image = medias_api.download(url: audio_link, file_path: 'tmp/downloaded_audio2.ogg', media_type: "audio/ogg")
|
70
|
+
puts "Downloaded: #{download_image.data.success?}"
|
71
|
+
|
72
|
+
|
73
|
+
|
56
74
|
############################## Business API ##############################
|
57
75
|
business_profile = business_profile_api.details(SENDER_ID)
|
58
76
|
business_profile_api.update(phone_number_id: SENDER_ID, params: { about: "A very cool business" } )
|
@@ -63,23 +81,39 @@ registered_numbers = phone_numbers_api.registered_numbers(BUSINESS_ID)
|
|
63
81
|
|
64
82
|
############################## Media API ##############################
|
65
83
|
|
66
|
-
|
84
|
+
##### Image #####
|
85
|
+
# upload a Image
|
67
86
|
uploaded_media = medias_api.upload(sender_id: SENDER_ID, file_path: "tmp/whatsapp.png", type: "image/png")
|
68
87
|
media_id = uploaded_media.data&.id
|
69
88
|
puts "Uploaded media id: #{media_id}"
|
70
89
|
|
71
|
-
# get a media
|
90
|
+
# get a media Image
|
72
91
|
media = medias_api.media(media_id: media_id).data
|
73
92
|
puts "Media info: #{media.raw_data_response}"
|
74
93
|
|
75
|
-
# download media
|
76
|
-
download_image = medias_api.download(url: media
|
94
|
+
# download media Image
|
95
|
+
download_image = medias_api.download(url: media.url, file_path: 'tmp/downloaded_image.png', media_type: "image/png")
|
77
96
|
puts "Downloaded: #{download_image.data.success?}"
|
78
97
|
|
79
98
|
# delete a media
|
80
99
|
deleted_media = medias_api.delete(media_id: media&.id)
|
81
100
|
puts "Deleted: #{deleted_media.data.success?}"
|
82
101
|
|
102
|
+
#### Audio ####
|
103
|
+
# upload an audio
|
104
|
+
uploaded_media = medias_api.upload(sender_id: SENDER_ID, file_path: "tmp/downloaded_audio.ogg", type: "audio/ogg")
|
105
|
+
media_id = uploaded_media.data&.id
|
106
|
+
puts "Uploaded media id: #{media_id}"
|
107
|
+
|
108
|
+
# get a media audio
|
109
|
+
media = medias_api.media(media_id: media_id).data
|
110
|
+
puts "Media info: #{media.raw_data_response}"
|
111
|
+
|
112
|
+
# get a media audio
|
113
|
+
audio_link = media.url
|
114
|
+
download_image = medias_api.download(url: audio_link, file_path: 'tmp/downloaded_audio2.ogg', media_type: "audio/ogg")
|
115
|
+
puts "Downloaded: #{download_image.data.success?}"
|
116
|
+
|
83
117
|
############################## Messages API ##############################
|
84
118
|
|
85
119
|
######### SEND A TEXT MESSAGE
|
@@ -35,18 +35,22 @@ module WhatsappSdk
|
|
35
35
|
JSON.parse(response.body)
|
36
36
|
end
|
37
37
|
|
38
|
-
sig
|
39
|
-
|
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)
|
40
43
|
uri = URI.parse(url)
|
41
44
|
request = Net::HTTP::Get.new(uri)
|
42
45
|
request["Authorization"] = "Bearer #{@access_token}"
|
46
|
+
request.content_type = content_header
|
43
47
|
req_options = { use_ssl: uri.scheme == "https" }
|
44
48
|
|
45
49
|
response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
|
46
50
|
http.request(request)
|
47
51
|
end
|
48
52
|
|
49
|
-
File.write(
|
53
|
+
File.write(file_path, response.body, mode: 'wb') if response.code == "200" && file_path
|
50
54
|
|
51
55
|
response
|
52
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
|
@@ -412,8 +412,8 @@ module WhatsappSdk
|
|
412
412
|
)
|
413
413
|
end
|
414
414
|
|
415
|
-
alias
|
416
|
-
alias
|
415
|
+
alias send_interactive_reply_buttons send_interactive_message
|
416
|
+
alias send_interactive_list_messages send_interactive_message
|
417
417
|
|
418
418
|
# Mark a message as read.
|
419
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: {})
|
@@ -61,7 +61,7 @@ module WhatsappSdk
|
|
61
61
|
sig do
|
62
62
|
params(
|
63
63
|
type: Type, buttons: T::Array[InteractiveActionReplyButton],
|
64
|
-
button: String, sections: T::Array[InteractiveActionSection]
|
64
|
+
button: String, sections: T::Array[InteractiveActionSection]
|
65
65
|
).void
|
66
66
|
end
|
67
67
|
def initialize(type:, buttons: [], button: "", sections: [])
|
@@ -93,40 +93,48 @@ module WhatsappSdk
|
|
93
93
|
def validate_fields
|
94
94
|
case type.serialize
|
95
95
|
when "list_message"
|
96
|
-
|
97
|
-
sections_count = sections.length
|
98
|
-
unless button_length > 0
|
99
|
-
raise WhatsappSdk::Resource::Error::InvalidInteractiveActionButton,
|
100
|
-
"Invalid button in action. Button label is required."
|
101
|
-
end
|
102
|
-
|
103
|
-
unless button_length <= LIST_BUTTON_TITLE_MAXIMUM
|
104
|
-
raise WhatsappSdk::Resource::Error::InvalidInteractiveActionButton,
|
105
|
-
"Invalid length #{button_length} for button. Maximum length: " \
|
106
|
-
"#{LIST_BUTTON_TITLE_MAXIMUM} characters."
|
107
|
-
end
|
108
|
-
|
109
|
-
unless (LIST_SECTIONS_MINIMUM..LIST_SECTIONS_MAXIMUM).cover?(sections_count)
|
110
|
-
raise WhatsappSdk::Resource::Error::InvalidInteractiveActionSection,
|
111
|
-
"Invalid length #{sections_count} for sections in action. It should be between " \
|
112
|
-
"#{LIST_SECTIONS_MINIMUM} and #{LIST_SECTIONS_MAXIMUM}."
|
113
|
-
end
|
114
|
-
|
115
|
-
sections.each { |section| section.validate }
|
96
|
+
validate_list_message
|
116
97
|
when "reply_button"
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
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
|
123
109
|
|
124
|
-
|
125
|
-
|
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
|
126
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)
|
127
128
|
raise WhatsappSdk::Resource::Error::InvalidInteractiveActionReplyButton,
|
128
|
-
"
|
129
|
+
"Invalid length #{buttons_count} for buttons in action. It should be between " \
|
130
|
+
"#{REPLY_BUTTONS_MINIMUM} and #{REPLY_BUTTONS_MAXIMUM}."
|
129
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."
|
130
138
|
end
|
131
139
|
end
|
132
140
|
end
|
@@ -34,12 +34,10 @@ module WhatsappSdk
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def to_json
|
37
|
-
|
37
|
+
{
|
38
38
|
title: title,
|
39
|
-
rows: rows.map(&:to_json)
|
39
|
+
rows: rows.map(&:to_json)
|
40
40
|
}
|
41
|
-
|
42
|
-
json
|
43
41
|
end
|
44
42
|
|
45
43
|
sig { params(skip_rows: T.nilable(T::Boolean)).void }
|
@@ -72,4 +70,3 @@ module WhatsappSdk
|
|
72
70
|
end
|
73
71
|
end
|
74
72
|
end
|
75
|
-
|
@@ -29,7 +29,7 @@ module WhatsappSdk
|
|
29
29
|
ACTION_SECTION_DESCRIPTION_MAXIMUM = 72
|
30
30
|
ACTION_SECTION_ID_MAXIMUM = 256
|
31
31
|
|
32
|
-
sig { params(title: String, id: String, description: T
|
32
|
+
sig { params(title: String, id: String, description: T.nilable(String)).void }
|
33
33
|
def initialize(title:, id:, description: "")
|
34
34
|
@title = title
|
35
35
|
@id = id
|
@@ -42,7 +42,7 @@ module WhatsappSdk
|
|
42
42
|
id: id,
|
43
43
|
title: title
|
44
44
|
}
|
45
|
-
json[:description] = description if description.length
|
45
|
+
json[:description] = description if description.length.positive?
|
46
46
|
|
47
47
|
json
|
48
48
|
end
|
@@ -61,9 +61,9 @@ module WhatsappSdk
|
|
61
61
|
title_length = title.length
|
62
62
|
return if title_length <= ACTION_SECTION_TITLE_MAXIMUM
|
63
63
|
|
64
|
-
raise WhatsappSdk::Resource::Error::InvalidInteractiveActionSectionRow
|
65
|
-
|
66
|
-
|
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
67
|
end
|
68
68
|
|
69
69
|
sig { void }
|
@@ -74,9 +74,9 @@ module WhatsappSdk
|
|
74
74
|
id_length = id.length
|
75
75
|
return if id_length <= ACTION_SECTION_ID_MAXIMUM
|
76
76
|
|
77
|
-
raise WhatsappSdk::Resource::Error::InvalidInteractiveActionSectionRow
|
78
|
-
|
79
|
-
|
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
80
|
end
|
81
81
|
|
82
82
|
sig { void }
|
@@ -84,11 +84,10 @@ module WhatsappSdk
|
|
84
84
|
description_length = description.length
|
85
85
|
return if description_length <= ACTION_SECTION_DESCRIPTION_MAXIMUM
|
86
86
|
|
87
|
-
raise WhatsappSdk::Resource::Error::InvalidInteractiveActionSectionRow
|
88
|
-
|
89
|
-
|
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
90
|
end
|
91
91
|
end
|
92
92
|
end
|
93
93
|
end
|
94
|
-
|
data/lib/whatsapp_sdk/version.rb
CHANGED
@@ -18,8 +18,8 @@ module Mocha::ClassMethods
|
|
18
18
|
end
|
19
19
|
|
20
20
|
class Mocha::Expectation
|
21
|
-
sig { params(
|
22
|
-
def with(*
|
21
|
+
sig { params(expected_parameters_or_matchers: T.untyped, kwargs: T.untyped, matching_block: T.nilable(T.proc.params(actual_parameters: T.untyped).void)).returns(Mocha::Expectation) }
|
22
|
+
def with(*expected_parameters_or_matchers, **kwargs, &matching_block); end
|
23
23
|
|
24
24
|
sig { params(values: T.untyped).returns(Mocha::Expectation) }
|
25
25
|
def returns(*values); end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: whatsapp_sdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ignacio-chiazzo
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-04-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|