chat_sdk-whatsapp 1.1.0 → 1.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 56d2740433f6135cc084664554536e8aada5dadc9784f495e63b744d572bb1b1
4
- data.tar.gz: 97cae7132efd2af77e10be202134f1430b0142477912745158294c7d297afc22
3
+ metadata.gz: d3aae7e3166ec158db8bb11d3a5fbd88e6df312846caf3208ba154ffbd65c200
4
+ data.tar.gz: 99c40c41b5cb2677c56add71a5717c18b8d91fcc3725b579e81e58f537d55186
5
5
  SHA512:
6
- metadata.gz: 58f760deb91563a1cd2cec6a32725fbdecf1249ad6116509ac6646321b36d116b0630220553c2b00c6dde71f8eb9583570cc6a02ffe6d9c386c47827091120a2
7
- data.tar.gz: 4475cb8cec45232adfe4a1090b08960071a8f041753b2e75d15f977791f35894285e2bd728be042aca550913e89fda9a87d9abadd0af0bc8fe1b700b9797173b
6
+ metadata.gz: 9848398b73448697e4432c5a669607a151823c479021e825558d73b1be6e48cae051be2572c9501d3f9060b95e9b38223082cc942e76cd9464261dd4432b7bb6
7
+ data.tar.gz: 0b5d5837929611cd82558baffdcace85decc3ed2dac403f5a32c1bc10c0c465d3002644a29be7e8a5b105c8e7c3a422010c74ddbf10208a5636d9c595e79cd9a
@@ -44,19 +44,20 @@ module ChatSDK
44
44
  end
45
45
 
46
46
  # Outbound
47
- def post_message(channel_id:, message:, thread_id: nil) # rubocop:disable Lint/UnusedMethodArgument
47
+ def post_message(channel_id:, message:, thread_id: nil)
48
+ phone_number_id = outbound_phone_number_id(channel_id, thread_id)
48
49
  payload = prepare_message_payload(message)
49
50
 
50
51
  result = if payload[:type] == "text" && payload.dig(:text, :body)
51
52
  chunks = split_message(payload[:text][:body])
52
53
  chunks.reduce(nil) do |_, chunk|
53
- @client.send_message(to: channel_id, type: "text", text: {body: chunk})
54
+ @client.send_message(to: channel_id, type: "text", phone_number_id: phone_number_id, text: {body: chunk})
54
55
  end
55
56
  else
56
- @client.send_message(to: channel_id, **payload)
57
+ @client.send_message(to: channel_id, phone_number_id: phone_number_id, **payload)
57
58
  end
58
59
 
59
- parse_whatsapp_message(result, channel_id)
60
+ parse_whatsapp_message(result, channel_id, phone_number_id: phone_number_id)
60
61
  end
61
62
 
62
63
  def self.template_components(header: nil, body_params: [], button_payloads: [])
@@ -88,19 +89,30 @@ module ChatSDK
88
89
  components
89
90
  end
90
91
 
91
- def post_template(channel_id:, template_name:, language_code: "en", components: nil)
92
- @client.send_template(to: channel_id, template_name: template_name, language_code: language_code, components: components)
92
+ def post_template(channel_id:, template_name:, language_code: "en", components: nil, thread_id: nil)
93
+ phone_number_id = outbound_phone_number_id(channel_id, thread_id)
94
+ @client.send_template(
95
+ to: channel_id,
96
+ template_name: template_name,
97
+ language_code: language_code,
98
+ components: components,
99
+ phone_number_id: phone_number_id
100
+ )
93
101
  end
94
102
 
95
103
  def mark_as_read(message_id:, channel_id: nil, thread_id: nil, message: nil) # rubocop:disable Lint/UnusedMethodArgument
96
- @client.mark_as_read(message_id: message_id)
104
+ @client.mark_as_read(
105
+ message_id: message_id,
106
+ phone_number_id: outbound_phone_number_id(channel_id, thread_id)
107
+ )
97
108
  end
98
109
 
99
- def reply_message(channel_id:, message_id:, message:, thread_id: nil) # rubocop:disable Lint/UnusedMethodArgument
110
+ def reply_message(channel_id:, message_id:, message:, thread_id: nil)
111
+ phone_number_id = outbound_phone_number_id(channel_id, thread_id)
100
112
  payload = prepare_message_payload(message)
101
113
  payload[:context] = {message_id: message_id}
102
- result = @client.send_message(to: channel_id, **payload)
103
- parse_whatsapp_message(result, channel_id)
114
+ result = @client.send_message(to: channel_id, phone_number_id: phone_number_id, **payload)
115
+ parse_whatsapp_message(result, channel_id, phone_number_id: phone_number_id)
104
116
  end
105
117
 
106
118
  def download_media(media_id:)
@@ -112,27 +124,48 @@ module ChatSDK
112
124
  response.body
113
125
  end
114
126
 
115
- def upload_file(channel_id:, io:, filename:, thread_id: nil, comment: nil) # rubocop:disable Lint/UnusedMethodArgument
127
+ def upload_file(channel_id:, io:, filename:, thread_id: nil, comment: nil)
128
+ phone_number_id = outbound_phone_number_id(channel_id, thread_id)
116
129
  content_type = detect_content_type(filename)
117
130
  media_type = media_type_for(content_type)
118
131
 
119
132
  # Upload media first
120
- media_result = @client.upload_media(io: io, filename: filename, content_type: content_type)
133
+ media_result = @client.upload_media(
134
+ io: io,
135
+ filename: filename,
136
+ content_type: content_type,
137
+ phone_number_id: phone_number_id
138
+ )
121
139
  media_id = media_result["id"]
122
140
 
123
141
  # Send media message
124
142
  media_payload = {caption: comment}.compact
125
- result = @client.send_message(to: channel_id, type: media_type, **{media_type => media_payload.merge(id: media_id)})
143
+ result = @client.send_message(
144
+ to: channel_id,
145
+ type: media_type,
146
+ phone_number_id: phone_number_id,
147
+ **{media_type => media_payload.merge(id: media_id)}
148
+ )
126
149
 
127
- parse_whatsapp_message(result, channel_id)
150
+ parse_whatsapp_message(result, channel_id, phone_number_id: phone_number_id)
128
151
  end
129
152
 
130
- def add_reaction(channel_id:, message_id:, emoji:)
131
- @client.send_reaction(to: channel_id, message_id: message_id, emoji: emoji)
153
+ def add_reaction(channel_id:, message_id:, emoji:, thread_id: nil)
154
+ @client.send_reaction(
155
+ to: channel_id,
156
+ message_id: message_id,
157
+ emoji: emoji,
158
+ phone_number_id: outbound_phone_number_id(channel_id, thread_id)
159
+ )
132
160
  end
133
161
 
134
- def remove_reaction(channel_id:, message_id:, emoji: "") # rubocop:disable Lint/UnusedMethodArgument
135
- @client.send_reaction(to: channel_id, message_id: message_id, emoji: "")
162
+ def remove_reaction(channel_id:, message_id:, emoji: "", thread_id: nil) # rubocop:disable Lint/UnusedMethodArgument
163
+ @client.send_reaction(
164
+ to: channel_id,
165
+ message_id: message_id,
166
+ emoji: "",
167
+ phone_number_id: outbound_phone_number_id(channel_id, thread_id)
168
+ )
136
169
  end
137
170
 
138
171
  def open_dm(user_id)
@@ -186,7 +219,7 @@ module ChatSDK
186
219
  end
187
220
  end
188
221
 
189
- def parse_whatsapp_message(data, channel_id)
222
+ def parse_whatsapp_message(data, channel_id, phone_number_id: @phone_number_id)
190
223
  message_id = data.dig("messages", 0, "id")
191
224
 
192
225
  ChatSDK::Message.new(
@@ -198,12 +231,28 @@ module ChatSDK
198
231
  platform: :whatsapp,
199
232
  bot: true
200
233
  ),
201
- thread_id: "whatsapp:#{@phone_number_id}:#{channel_id}",
234
+ thread_id: "whatsapp:#{phone_number_id}:#{channel_id}",
202
235
  channel_id: channel_id,
203
236
  platform: :whatsapp,
204
237
  raw: data
205
238
  )
206
239
  end
240
+
241
+ def outbound_phone_number_id(channel_id, thread_id)
242
+ return @phone_number_id unless thread_id
243
+
244
+ match = thread_id.match(/\Awhatsapp:([^:]+):(.+)\z/) if thread_id.is_a?(String)
245
+ phone_number_id = match && match[1]
246
+ unless phone_number_id&.match?(/\A\d+\z/)
247
+ raise ChatSDK::Error, "Invalid WhatsApp phone number ID in thread ID: #{thread_id.inspect}"
248
+ end
249
+
250
+ unless channel_id.nil? || match[2] == channel_id.to_s
251
+ raise ChatSDK::Error, "WhatsApp thread recipient does not match channel ID: #{thread_id.inspect}"
252
+ end
253
+
254
+ phone_number_id
255
+ end
207
256
  end
208
257
  end
209
258
  end
@@ -15,7 +15,7 @@ module ChatSDK
15
15
  @phone_number_id = phone_number_id
16
16
  end
17
17
 
18
- def send_message(to:, type:, **payload)
18
+ def send_message(to:, type:, phone_number_id: @phone_number_id, **payload)
19
19
  body = {
20
20
  "messaging_product" => "whatsapp",
21
21
  "recipient_type" => "individual",
@@ -23,18 +23,19 @@ module ChatSDK
23
23
  "type" => type
24
24
  }.merge(payload)
25
25
 
26
- request(:post, "#{@phone_number_id}/messages", body)
26
+ request(:post, "#{phone_number_id}/messages", body)
27
27
  end
28
28
 
29
- def send_reaction(to:, message_id:, emoji:)
29
+ def send_reaction(to:, message_id:, emoji:, phone_number_id: @phone_number_id)
30
30
  send_message(
31
31
  to: to,
32
32
  type: "reaction",
33
+ phone_number_id: phone_number_id,
33
34
  reaction: {message_id: message_id, emoji: emoji}
34
35
  )
35
36
  end
36
37
 
37
- def send_template(to:, template_name:, language_code: "en", components: nil)
38
+ def send_template(to:, template_name:, language_code: "en", components: nil, phone_number_id: @phone_number_id)
38
39
  body = {
39
40
  "messaging_product" => "whatsapp",
40
41
  "to" => to,
@@ -45,11 +46,11 @@ module ChatSDK
45
46
  }
46
47
  }
47
48
  body["template"]["components"] = components if components
48
- request(:post, "#{@phone_number_id}/messages", body)
49
+ request(:post, "#{phone_number_id}/messages", body)
49
50
  end
50
51
 
51
- def mark_as_read(message_id:)
52
- request(:post, "#{@phone_number_id}/messages", {
52
+ def mark_as_read(message_id:, phone_number_id: @phone_number_id)
53
+ request(:post, "#{phone_number_id}/messages", {
53
54
  "messaging_product" => "whatsapp",
54
55
  "status" => "read",
55
56
  "message_id" => message_id
@@ -69,8 +70,8 @@ module ChatSDK
69
70
  .tap { |response| validate_media_response!(response) }
70
71
  end
71
72
 
72
- def upload_media(io:, filename:, content_type:)
73
- response = upload_connection.post("#{@phone_number_id}/media") do |req|
73
+ def upload_media(io:, filename:, content_type:, phone_number_id: @phone_number_id)
74
+ response = upload_connection.post("#{phone_number_id}/media") do |req|
74
75
  req.body = {
75
76
  "messaging_product" => "whatsapp",
76
77
  "file" => Faraday::Multipart::FilePart.new(io, content_type, filename),
@@ -11,14 +11,21 @@ module ChatSDK
11
11
  (payload["entry"] || [])
12
12
  .flat_map { |entry| entry["changes"] || [] }
13
13
  .select { |change| change["field"] == "messages" }
14
- .flat_map { |change| change.dig("value", "messages") || [] }
15
- .filter_map { |msg| parse_message(msg, phone_number_id) }
14
+ .flat_map do |change|
15
+ value = change["value"] || {}
16
+ contacts = value["contacts"] || []
17
+ (value["messages"] || []).filter_map do |msg|
18
+ contact = contact_for(msg, contacts)
19
+ parse_message(msg, phone_number_id, contact)
20
+ end
21
+ end
16
22
  end
17
23
 
18
24
  private
19
25
 
20
- def parse_message(msg, phone_number_id)
21
- from = msg["from"]&.to_s
26
+ def parse_message(msg, phone_number_id, contact)
27
+ from = first_present(msg["from"], msg["wa_id"], contact&.dig("wa_id"),
28
+ msg["from_user_id"], contact&.dig("user_id"))
22
29
  return nil unless from
23
30
 
24
31
  author = ChatSDK::Author.new(id: from, name: from, platform: :whatsapp, bot: false)
@@ -36,6 +43,16 @@ module ChatSDK
36
43
  end
37
44
  end
38
45
 
46
+ def contact_for(message, contacts)
47
+ from = message["from"].to_s
48
+ contacts.find { |contact| contact["wa_id"].to_s == from } ||
49
+ (contacts.first if contacts.length == 1)
50
+ end
51
+
52
+ def first_present(*values)
53
+ values.map { |value| value&.to_s }.find { |value| value && !value.empty? }
54
+ end
55
+
39
56
  def parse_reaction_message(msg, from, thread_id)
40
57
  reaction = msg["reaction"] || {}
41
58
  emoji = reaction["emoji"] || ""
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chat_sdk-whatsapp
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Quentin Rousseau
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '0.1'
18
+ version: '1.2'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: '0.1'
25
+ version: '1.2'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: faraday
28
28
  requirement: !ruby/object:Gem::Requirement