ruby-whatsapp 0.4.2 → 0.6.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +158 -0
- data/README.md +70 -7
- data/docs/README.md +4 -1
- data/docs/business_phone_number/README.md +12 -1
- data/docs/business_phone_number/contact_book.md +90 -0
- data/docs/business_phone_number/username.md +157 -0
- data/docs/configuration.md +6 -1
- data/docs/errors.md +12 -1
- data/docs/message_templates/README.md +1 -1
- data/docs/message_templates/components.md +37 -1
- data/docs/messages/README.md +86 -9
- data/docs/messages/address.md +1 -1
- data/docs/messages/audio.md +1 -1
- data/docs/messages/contacts.md +1 -1
- data/docs/messages/document.md +1 -1
- data/docs/messages/image.md +1 -1
- data/docs/messages/interactive.md +84 -4
- data/docs/messages/location.md +1 -1
- data/docs/messages/location_request.md +1 -1
- data/docs/messages/reaction.md +1 -1
- data/docs/messages/sticker.md +1 -1
- data/docs/messages/template.md +28 -1
- data/docs/messages/text.md +1 -1
- data/docs/messages/video.md +1 -1
- data/docs/schedule/README.md +183 -0
- data/docs/webhooks/README.md +20 -12
- data/docs/webhooks/business_username_updates.md +55 -0
- data/docs/webhooks/history.md +78 -15
- data/docs/webhooks/messages.md +141 -6
- data/docs/webhooks/smb_app_state_sync.md +50 -12
- data/docs/webhooks/smb_message_echoes.md +58 -21
- data/docs/webhooks/user_id_update.md +84 -0
- data/docs/webhooks/user_preferences.md +55 -16
- data/lib/ruby/whatsapp/business_phone_number/contact_book/delete.rb +87 -0
- data/lib/ruby/whatsapp/business_phone_number/contact_book/deletion.rb +50 -0
- data/lib/ruby/whatsapp/business_phone_number/contact_book.rb +25 -0
- data/lib/ruby/whatsapp/business_phone_number/transport.rb +6 -5
- data/lib/ruby/whatsapp/business_phone_number/username/claim.rb +180 -0
- data/lib/ruby/whatsapp/business_phone_number/username/delete.rb +36 -0
- data/lib/ruby/whatsapp/business_phone_number/username/details.rb +66 -0
- data/lib/ruby/whatsapp/business_phone_number/username/get.rb +33 -0
- data/lib/ruby/whatsapp/business_phone_number/username/suggest.rb +37 -0
- data/lib/ruby/whatsapp/business_phone_number/username/suggestions.rb +50 -0
- data/lib/ruby/whatsapp/business_phone_number/username.rb +27 -0
- data/lib/ruby/whatsapp/business_phone_number.rb +9 -0
- data/lib/ruby/whatsapp/message_templates/button/request_contact_info.rb +66 -0
- data/lib/ruby/whatsapp/message_templates/button.rb +5 -2
- data/lib/ruby/whatsapp/message_templates/component_set.rb +28 -0
- data/lib/ruby/whatsapp/message_templates/response/paging.rb +2 -1
- data/lib/ruby/whatsapp/messages/base.rb +47 -5
- data/lib/ruby/whatsapp/messages/interactive/list_buttons/section.rb +11 -6
- data/lib/ruby/whatsapp/messages/interactive/list_buttons.rb +12 -0
- data/lib/ruby/whatsapp/messages/interactive/request_contact_info.rb +59 -0
- data/lib/ruby/whatsapp/messages/interactive.rb +22 -4
- data/lib/ruby/whatsapp/messages/response/contacts.rb +22 -5
- data/lib/ruby/whatsapp/schedule/create.rb +101 -0
- data/lib/ruby/whatsapp/schedule/list.rb +50 -0
- data/lib/ruby/whatsapp/schedule/response/collection.rb +61 -0
- data/lib/ruby/whatsapp/schedule/response/created.rb +30 -0
- data/lib/ruby/whatsapp/schedule/response/node.rb +85 -0
- data/lib/ruby/whatsapp/schedule/response.rb +18 -0
- data/lib/ruby/whatsapp/schedule/statuses.rb +19 -0
- data/lib/ruby/whatsapp/schedule/transport.rb +24 -0
- data/lib/ruby/whatsapp/schedule.rb +15 -0
- data/lib/ruby/whatsapp/version.rb +1 -1
- data/lib/ruby/whatsapp/webhook/business_username_updates.rb +51 -0
- data/lib/ruby/whatsapp/webhook/change.rb +3 -1
- data/lib/ruby/whatsapp/webhook/contact.rb +26 -3
- data/lib/ruby/whatsapp/webhook/history/chunk.rb +50 -0
- data/lib/ruby/whatsapp/webhook/history/thread.rb +64 -0
- data/lib/ruby/whatsapp/webhook/history.rb +73 -34
- data/lib/ruby/whatsapp/webhook/message/base.rb +27 -2
- data/lib/ruby/whatsapp/webhook/message/contacts/contact.rb +15 -2
- data/lib/ruby/whatsapp/webhook/message/context.rb +23 -2
- data/lib/ruby/whatsapp/webhook/message/edit.rb +50 -0
- data/lib/ruby/whatsapp/webhook/message/revoke.rb +36 -0
- data/lib/ruby/whatsapp/webhook/message/system.rb +19 -2
- data/lib/ruby/whatsapp/webhook/message/unknown.rb +2 -2
- data/lib/ruby/whatsapp/webhook/message.rb +3 -0
- data/lib/ruby/whatsapp/webhook/messages.rb +4 -3
- data/lib/ruby/whatsapp/webhook/smb_app_state_sync/contact.rb +76 -0
- data/lib/ruby/whatsapp/webhook/smb_app_state_sync/state_sync.rb +18 -4
- data/lib/ruby/whatsapp/webhook/smb_app_state_sync.rb +33 -9
- data/lib/ruby/whatsapp/webhook/smb_message_echoes/echo.rb +60 -0
- data/lib/ruby/whatsapp/webhook/smb_message_echoes.rb +26 -11
- data/lib/ruby/whatsapp/webhook/status.rb +23 -2
- data/lib/ruby/whatsapp/webhook/user_id_update/entry.rb +56 -0
- data/lib/ruby/whatsapp/webhook/user_id_update/id_change.rb +35 -0
- data/lib/ruby/whatsapp/webhook/user_id_update.rb +61 -0
- data/lib/ruby/whatsapp/webhook/user_preferences/entry.rb +76 -0
- data/lib/ruby/whatsapp/webhook/user_preferences.rb +44 -23
- metadata +38 -1
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Whatsapp
|
|
4
4
|
module Webhook
|
|
5
|
-
# The `messages` webhook field's `value` payload —
|
|
6
|
-
# confirmed, JSON-example-backed schema in Meta's docs
|
|
7
|
-
# messages and/or delivery status
|
|
5
|
+
# The `messages` webhook field's `value` payload — one of five fields with a
|
|
6
|
+
# confirmed, JSON-example-backed schema in Meta's docs, alongside {UserIdUpdate}
|
|
7
|
+
# and {BusinessUsernameUpdates}. Carries inbound messages and/or delivery status
|
|
8
|
+
# updates for one of your business phone numbers.
|
|
8
9
|
# Source: https://developers.facebook.com/documentation/business-messaging/whatsapp/webhooks/overview
|
|
9
10
|
class Messages
|
|
10
11
|
# @!attribute [rw] messaging_product
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Whatsapp
|
|
4
|
+
module Webhook
|
|
5
|
+
class SmbAppStateSync
|
|
6
|
+
# A synced address-book entry from the WhatsApp Business app.
|
|
7
|
+
#
|
|
8
|
+
# The gem's third `Contact`, and none of them are interchangeable:
|
|
9
|
+
# {Whatsapp::Webhook::Contact} is a notification's sender identity (`profile.name`,
|
|
10
|
+
# `wa_id`), {Whatsapp::Webhook::Message::Contacts::Contact} is a shared contact
|
|
11
|
+
# *card* of vCard-shaped arrays, and this is a device address-book row
|
|
12
|
+
# (`full_name`, `first_name`, `phone_number`). Forcing any two together would mean
|
|
13
|
+
# dual-key reads on both sides for no gain.
|
|
14
|
+
#
|
|
15
|
+
# A sibling of {StateSync} rather than nested inside it, following
|
|
16
|
+
# {UserIdUpdate::IdChange}.
|
|
17
|
+
class Contact
|
|
18
|
+
# @!attribute [rw] full_name
|
|
19
|
+
# @return [String, nil]
|
|
20
|
+
attr_accessor :full_name
|
|
21
|
+
|
|
22
|
+
# @!attribute [rw] first_name
|
|
23
|
+
# @return [String, nil]
|
|
24
|
+
attr_accessor :first_name
|
|
25
|
+
|
|
26
|
+
# @!attribute [rw] phone_number
|
|
27
|
+
# @return [String, nil] Omitted once the contact adopts a username and their
|
|
28
|
+
# number is no longer shareable. Fall back to {#user_id}.
|
|
29
|
+
attr_accessor :phone_number
|
|
30
|
+
|
|
31
|
+
# @!attribute [rw] user_id
|
|
32
|
+
# @return [String, nil] The contact's business-scoped user ID (BSUID).
|
|
33
|
+
attr_accessor :user_id
|
|
34
|
+
|
|
35
|
+
# @!attribute [rw] parent_user_id
|
|
36
|
+
# @return [String, nil] The contact's parent BSUID, present only when enrolled.
|
|
37
|
+
attr_accessor :parent_user_id
|
|
38
|
+
|
|
39
|
+
# @!attribute [rw] username
|
|
40
|
+
# @return [String, nil] Set only if the contact has adopted a username. Read
|
|
41
|
+
# flat, unlike {Whatsapp::Webhook::Contact#username}, which Meta nests under
|
|
42
|
+
# `profile` — do not copy that dual read here.
|
|
43
|
+
attr_accessor :username
|
|
44
|
+
|
|
45
|
+
def initialize(
|
|
46
|
+
full_name: nil, first_name: nil, phone_number: nil,
|
|
47
|
+
user_id: nil, parent_user_id: nil, username: nil
|
|
48
|
+
)
|
|
49
|
+
@full_name = full_name
|
|
50
|
+
@first_name = first_name
|
|
51
|
+
@phone_number = phone_number
|
|
52
|
+
@user_id = user_id
|
|
53
|
+
@parent_user_id = parent_user_id
|
|
54
|
+
@username = username
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class << self
|
|
58
|
+
# @param data [Hash, nil] A raw `contact` hash.
|
|
59
|
+
# @return [Contact]
|
|
60
|
+
def deserialize(data)
|
|
61
|
+
data ||= {}
|
|
62
|
+
|
|
63
|
+
new(
|
|
64
|
+
full_name: data["full_name"],
|
|
65
|
+
first_name: data["first_name"],
|
|
66
|
+
phone_number: data["phone_number"],
|
|
67
|
+
user_id: data["user_id"],
|
|
68
|
+
parent_user_id: data["parent_user_id"],
|
|
69
|
+
username: data["username"]
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -6,7 +6,7 @@ module Whatsapp
|
|
|
6
6
|
# A single contact-sync entry within an `smb_app_state_sync` notification.
|
|
7
7
|
class StateSync
|
|
8
8
|
# @!attribute [rw] type
|
|
9
|
-
# @return [String, nil]
|
|
9
|
+
# @return [String, nil] `"contact"` is the only kind Meta documents.
|
|
10
10
|
attr_accessor :type
|
|
11
11
|
|
|
12
12
|
# @!attribute [rw] action
|
|
@@ -14,13 +14,22 @@ module Whatsapp
|
|
|
14
14
|
attr_accessor :action
|
|
15
15
|
|
|
16
16
|
# @!attribute [rw] contact
|
|
17
|
-
# @return [
|
|
17
|
+
# @return [Contact, nil] The synced address-book entry.
|
|
18
18
|
attr_accessor :contact
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
# @!attribute [rw] timestamp
|
|
21
|
+
# @return [String, nil] Flattened up from this entry's own `metadata.timestamp`.
|
|
22
|
+
# That nested object is *not* {Whatsapp::Webhook::Metadata} — it carries only a
|
|
23
|
+
# timestamp, so the wrapper is dropped rather than typed, the same habit as
|
|
24
|
+
# {Contact#profile_name}. Should Meta add a sibling key there, this becomes a
|
|
25
|
+
# class instead.
|
|
26
|
+
attr_accessor :timestamp
|
|
27
|
+
|
|
28
|
+
def initialize(type:, action:, contact: nil, timestamp: nil)
|
|
21
29
|
@type = type
|
|
22
30
|
@action = action
|
|
23
31
|
@contact = contact
|
|
32
|
+
@timestamp = timestamp
|
|
24
33
|
end
|
|
25
34
|
|
|
26
35
|
class << self
|
|
@@ -29,7 +38,12 @@ module Whatsapp
|
|
|
29
38
|
def deserialize(data)
|
|
30
39
|
data ||= {}
|
|
31
40
|
|
|
32
|
-
new(
|
|
41
|
+
new(
|
|
42
|
+
type: data["type"],
|
|
43
|
+
action: data["action"],
|
|
44
|
+
contact: data["contact"] ? Contact.deserialize(data["contact"]) : nil,
|
|
45
|
+
timestamp: data.dig("metadata", "timestamp")
|
|
46
|
+
)
|
|
33
47
|
end
|
|
34
48
|
end
|
|
35
49
|
end
|
|
@@ -2,21 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
module Whatsapp
|
|
4
4
|
module Webhook
|
|
5
|
-
# Contact synchronization for onboarded WhatsApp Business app users
|
|
5
|
+
# Contact synchronization for onboarded WhatsApp Business app users: the address book
|
|
6
|
+
# on the device, mirrored to your integration as entries are added, changed, or removed.
|
|
6
7
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# this in production.
|
|
12
|
-
# Source: https://developers.facebook.com/documentation/business-messaging/whatsapp/webhooks/overview
|
|
8
|
+
# Meta's BSUID documentation publishes the synced contact's shape, so
|
|
9
|
+
# {StateSync#contact} is a typed {Contact} rather than a raw hash. The array key
|
|
10
|
+
# itself (`state_sync`) is still inferred from the field name — hence {#raw}.
|
|
11
|
+
# Source: https://developers.facebook.com/documentation/business-messaging/whatsapp/business-scoped-user-ids
|
|
13
12
|
class SmbAppStateSync
|
|
13
|
+
# @!attribute [rw] messaging_product
|
|
14
|
+
# @return [String, nil]
|
|
15
|
+
attr_accessor :messaging_product
|
|
16
|
+
|
|
17
|
+
# @!attribute [rw] metadata
|
|
18
|
+
# @return [Metadata, nil] The business phone number whose address book this is —
|
|
19
|
+
# not the per-entry metadata, whose timestamp is flattened onto {StateSync}.
|
|
20
|
+
attr_accessor :metadata
|
|
21
|
+
|
|
14
22
|
# @!attribute [rw] state_sync
|
|
15
23
|
# @return [Array<StateSync>]
|
|
16
24
|
attr_accessor :state_sync
|
|
17
25
|
|
|
18
|
-
|
|
26
|
+
# @!attribute [r] raw
|
|
27
|
+
# @return [Hash] The untouched `value` hash. A diagnostic escape hatch, not part of
|
|
28
|
+
# the supported API: {#state_sync} reads `value["state_sync"]`, a key inferred
|
|
29
|
+
# from the field's own name rather than confirmed by a published example, so a
|
|
30
|
+
# key that drifts would leave the array empty with no other trace. Read-only —
|
|
31
|
+
# it is the object `JSON.parse` returned, not a copy.
|
|
32
|
+
attr_reader :raw
|
|
33
|
+
|
|
34
|
+
def initialize(state_sync:, messaging_product: nil, metadata: nil, raw: {})
|
|
35
|
+
@messaging_product = messaging_product
|
|
36
|
+
@metadata = metadata
|
|
19
37
|
@state_sync = state_sync
|
|
38
|
+
@raw = raw
|
|
20
39
|
end
|
|
21
40
|
|
|
22
41
|
class << self
|
|
@@ -25,7 +44,12 @@ module Whatsapp
|
|
|
25
44
|
def deserialize(data)
|
|
26
45
|
data ||= {}
|
|
27
46
|
|
|
28
|
-
new(
|
|
47
|
+
new(
|
|
48
|
+
messaging_product: data["messaging_product"],
|
|
49
|
+
metadata: data["metadata"] ? Metadata.deserialize(data["metadata"]) : nil,
|
|
50
|
+
state_sync: Array(data["state_sync"]).map { |entry| StateSync.deserialize(entry) },
|
|
51
|
+
raw: data
|
|
52
|
+
)
|
|
29
53
|
end
|
|
30
54
|
end
|
|
31
55
|
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Whatsapp
|
|
4
|
+
module Webhook
|
|
5
|
+
class SmbMessageEchoes
|
|
6
|
+
# One echoed message: who the business sent it *to*, plus the message itself.
|
|
7
|
+
#
|
|
8
|
+
# A wrapper rather than three more accessors on {Message::Base}, deliberately.
|
|
9
|
+
# `Base` is the envelope shared by every inbound message type, and an inbound
|
|
10
|
+
# message can never carry a recipient — Meta's own convention is that a keyword
|
|
11
|
+
# defaults to `nil` when a field is *conditional*, not when it is impossible.
|
|
12
|
+
# Putting `to`/`to_user_id`/`to_parent_user_id` there to serve this one field would
|
|
13
|
+
# hand fifteen classes three permanently-`nil` readers, and would turn the
|
|
14
|
+
# `NoMethodError` that currently tells you `message.to` is meaningless into a silent
|
|
15
|
+
# `nil`. Composing instead keeps {Message.deserialize}'s dispatch untouched, so an
|
|
16
|
+
# echoed `revoke` or `edit` resolves exactly as an inbound one does.
|
|
17
|
+
class Echo
|
|
18
|
+
# @!attribute [rw] to
|
|
19
|
+
# @return [String, nil] The customer's phone number. Omitted once they adopt a
|
|
20
|
+
# username and it is no longer shareable — fall back to {#to_user_id}.
|
|
21
|
+
attr_accessor :to
|
|
22
|
+
|
|
23
|
+
# @!attribute [rw] to_user_id
|
|
24
|
+
# @return [String, nil] The customer's business-scoped user ID (BSUID).
|
|
25
|
+
attr_accessor :to_user_id
|
|
26
|
+
|
|
27
|
+
# @!attribute [rw] to_parent_user_id
|
|
28
|
+
# @return [String, nil] The customer's parent BSUID, present only when enrolled.
|
|
29
|
+
attr_accessor :to_parent_user_id
|
|
30
|
+
|
|
31
|
+
# @!attribute [rw] message
|
|
32
|
+
# @return [Message::Base] The echoed message, dispatched by `type` like any
|
|
33
|
+
# inbound one. Its `from` is the *business* number, not the customer's.
|
|
34
|
+
attr_accessor :message
|
|
35
|
+
|
|
36
|
+
def initialize(message:, to: nil, to_user_id: nil, to_parent_user_id: nil)
|
|
37
|
+
@to = to
|
|
38
|
+
@to_user_id = to_user_id
|
|
39
|
+
@to_parent_user_id = to_parent_user_id
|
|
40
|
+
@message = message
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class << self
|
|
44
|
+
# @param data [Hash, nil] A raw `message_echoes[]` entry.
|
|
45
|
+
# @return [Echo]
|
|
46
|
+
def deserialize(data)
|
|
47
|
+
data ||= {}
|
|
48
|
+
|
|
49
|
+
new(
|
|
50
|
+
to: data["to"],
|
|
51
|
+
to_user_id: data["to_user_id"],
|
|
52
|
+
to_parent_user_id: data["to_parent_user_id"],
|
|
53
|
+
message: Message.deserialize(data)
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -2,15 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
module Whatsapp
|
|
4
4
|
module Webhook
|
|
5
|
-
# Messages sent via the WhatsApp Business App or a linked device, echoed back
|
|
6
|
-
#
|
|
5
|
+
# Messages sent via the WhatsApp Business App or a linked device, echoed back so a
|
|
6
|
+
# Cloud API integration stays in sync with what the business itself sent — including
|
|
7
|
+
# messages the business revoked or edited from the app.
|
|
7
8
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# payload before relying on it in production.
|
|
13
|
-
# Source: https://developers.facebook.com/documentation/business-messaging/whatsapp/webhooks/overview
|
|
9
|
+
# Meta's BSUID documentation publishes the value-level `contacts` array and the
|
|
10
|
+
# per-echo recipient identifiers. The array key itself (`message_echoes`) remains
|
|
11
|
+
# inferred, hence {#raw}.
|
|
12
|
+
# Source: https://developers.facebook.com/documentation/business-messaging/whatsapp/business-scoped-user-ids
|
|
14
13
|
class SmbMessageEchoes
|
|
15
14
|
# @!attribute [rw] messaging_product
|
|
16
15
|
# @return [String, nil]
|
|
@@ -20,14 +19,28 @@ module Whatsapp
|
|
|
20
19
|
# @return [Metadata, nil]
|
|
21
20
|
attr_accessor :metadata
|
|
22
21
|
|
|
22
|
+
# @!attribute [rw] contacts
|
|
23
|
+
# @return [Array<Contact>] The customers these messages went to.
|
|
24
|
+
attr_accessor :contacts
|
|
25
|
+
|
|
23
26
|
# @!attribute [rw] message_echoes
|
|
24
|
-
# @return [Array<
|
|
27
|
+
# @return [Array<Echo>] Each pairs the echoed message with who it was sent to.
|
|
25
28
|
attr_accessor :message_echoes
|
|
26
29
|
|
|
27
|
-
|
|
30
|
+
# @!attribute [r] raw
|
|
31
|
+
# @return [Hash] The untouched `value` hash. A diagnostic escape hatch, not part of
|
|
32
|
+
# the supported API: {#message_echoes} reads `value["message_echoes"]`, a key
|
|
33
|
+
# inferred from the field's own name rather than confirmed by a published
|
|
34
|
+
# example, so a key that drifts would leave the array empty with no other trace.
|
|
35
|
+
# Read-only — it is the object `JSON.parse` returned, not a copy.
|
|
36
|
+
attr_reader :raw
|
|
37
|
+
|
|
38
|
+
def initialize(messaging_product:, metadata:, message_echoes:, contacts: [], raw: {})
|
|
28
39
|
@messaging_product = messaging_product
|
|
29
40
|
@metadata = metadata
|
|
41
|
+
@contacts = contacts
|
|
30
42
|
@message_echoes = message_echoes
|
|
43
|
+
@raw = raw
|
|
31
44
|
end
|
|
32
45
|
|
|
33
46
|
class << self
|
|
@@ -39,7 +52,9 @@ module Whatsapp
|
|
|
39
52
|
new(
|
|
40
53
|
messaging_product: data["messaging_product"],
|
|
41
54
|
metadata: data["metadata"] ? Metadata.deserialize(data["metadata"]) : nil,
|
|
42
|
-
|
|
55
|
+
contacts: Array(data["contacts"]).map { |contact_data| Contact.deserialize(contact_data) },
|
|
56
|
+
message_echoes: Array(data["message_echoes"]).map { |echo_data| Echo.deserialize(echo_data) },
|
|
57
|
+
raw: data
|
|
43
58
|
)
|
|
44
59
|
end
|
|
45
60
|
end
|
|
@@ -18,9 +18,22 @@ module Whatsapp
|
|
|
18
18
|
attr_accessor :timestamp
|
|
19
19
|
|
|
20
20
|
# @!attribute [rw] recipient_id
|
|
21
|
-
# @return [String, nil]
|
|
21
|
+
# @return [String, nil] Omitted when sent to the recipient's BSUID and their
|
|
22
|
+
# phone number is unavailable.
|
|
22
23
|
attr_accessor :recipient_id
|
|
23
24
|
|
|
25
|
+
# @!attribute [rw] recipient_user_id
|
|
26
|
+
# @return [String, nil] The recipient's business-scoped user ID (BSUID). Meta sends
|
|
27
|
+
# it on every status, however the message was addressed — a phone number in `to`
|
|
28
|
+
# does not suppress it. The single exception is a `"failed"` status for a message
|
|
29
|
+
# sent to a phone number, where it is omitted; a `"failed"` status for a
|
|
30
|
+
# BSUID-addressed message still carries it.
|
|
31
|
+
attr_accessor :recipient_user_id
|
|
32
|
+
|
|
33
|
+
# @!attribute [rw] recipient_parent_user_id
|
|
34
|
+
# @return [String, nil] The recipient's parent BSUID, present only when enrolled.
|
|
35
|
+
attr_accessor :recipient_parent_user_id
|
|
36
|
+
|
|
24
37
|
# @!attribute [rw] conversation
|
|
25
38
|
# @return [Conversation, nil]
|
|
26
39
|
attr_accessor :conversation
|
|
@@ -33,11 +46,17 @@ module Whatsapp
|
|
|
33
46
|
# @return [Array<Error>]
|
|
34
47
|
attr_accessor :errors
|
|
35
48
|
|
|
36
|
-
def initialize(
|
|
49
|
+
def initialize(
|
|
50
|
+
id:, status:, timestamp:,
|
|
51
|
+
recipient_id: nil, recipient_user_id: nil, recipient_parent_user_id: nil,
|
|
52
|
+
conversation: nil, pricing: nil, errors: []
|
|
53
|
+
)
|
|
37
54
|
@id = id
|
|
38
55
|
@status = status
|
|
39
56
|
@timestamp = timestamp
|
|
40
57
|
@recipient_id = recipient_id
|
|
58
|
+
@recipient_user_id = recipient_user_id
|
|
59
|
+
@recipient_parent_user_id = recipient_parent_user_id
|
|
41
60
|
@conversation = conversation
|
|
42
61
|
@pricing = pricing
|
|
43
62
|
@errors = errors
|
|
@@ -54,6 +73,8 @@ module Whatsapp
|
|
|
54
73
|
status: data["status"],
|
|
55
74
|
timestamp: data["timestamp"],
|
|
56
75
|
recipient_id: data["recipient_id"],
|
|
76
|
+
recipient_user_id: data["recipient_user_id"],
|
|
77
|
+
recipient_parent_user_id: data["recipient_parent_user_id"],
|
|
57
78
|
conversation: data["conversation"] ? Conversation.deserialize(data["conversation"]) : nil,
|
|
58
79
|
pricing: data["pricing"] ? Pricing.deserialize(data["pricing"]) : nil,
|
|
59
80
|
errors: Array(data["errors"]).map { |error_data| Error.deserialize(error_data) }
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Whatsapp
|
|
4
|
+
module Webhook
|
|
5
|
+
class UserIdUpdate
|
|
6
|
+
# A single `user_id_update[]` entry: one user's BSUID (and parent BSUID, if
|
|
7
|
+
# enrolled) changed.
|
|
8
|
+
class Entry
|
|
9
|
+
# @!attribute [rw] wa_id
|
|
10
|
+
# @return [String, nil] The user's phone number, if available.
|
|
11
|
+
attr_accessor :wa_id
|
|
12
|
+
|
|
13
|
+
# @!attribute [rw] detail
|
|
14
|
+
# @return [String, nil] Human-readable description of the change.
|
|
15
|
+
attr_accessor :detail
|
|
16
|
+
|
|
17
|
+
# @!attribute [rw] user_id
|
|
18
|
+
# @return [IdChange, nil] The user's previous and current BSUID.
|
|
19
|
+
attr_accessor :user_id
|
|
20
|
+
|
|
21
|
+
# @!attribute [rw] parent_user_id
|
|
22
|
+
# @return [IdChange, nil] The user's previous and current parent BSUID,
|
|
23
|
+
# present only when enrolled.
|
|
24
|
+
attr_accessor :parent_user_id
|
|
25
|
+
|
|
26
|
+
# @!attribute [rw] timestamp
|
|
27
|
+
# @return [String, nil]
|
|
28
|
+
attr_accessor :timestamp
|
|
29
|
+
|
|
30
|
+
def initialize(timestamp:, wa_id: nil, detail: nil, user_id: nil, parent_user_id: nil)
|
|
31
|
+
@wa_id = wa_id
|
|
32
|
+
@detail = detail
|
|
33
|
+
@user_id = user_id
|
|
34
|
+
@parent_user_id = parent_user_id
|
|
35
|
+
@timestamp = timestamp
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class << self
|
|
39
|
+
# @param data [Hash] A raw `user_id_update[]` entry.
|
|
40
|
+
# @return [Entry]
|
|
41
|
+
def deserialize(data)
|
|
42
|
+
data ||= {}
|
|
43
|
+
|
|
44
|
+
new(
|
|
45
|
+
wa_id: data["wa_id"],
|
|
46
|
+
detail: data["detail"],
|
|
47
|
+
user_id: IdChange.deserialize(data["user_id"]),
|
|
48
|
+
parent_user_id: IdChange.deserialize(data["parent_user_id"]),
|
|
49
|
+
timestamp: data["timestamp"]
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Whatsapp
|
|
4
|
+
module Webhook
|
|
5
|
+
class UserIdUpdate
|
|
6
|
+
# The `{previous, current}` shape shared by `user_id` and `parent_user_id` on a
|
|
7
|
+
# `user_id_update[]` entry.
|
|
8
|
+
class IdChange
|
|
9
|
+
# @!attribute [rw] previous
|
|
10
|
+
# @return [String, nil]
|
|
11
|
+
attr_accessor :previous
|
|
12
|
+
|
|
13
|
+
# @!attribute [rw] current
|
|
14
|
+
# @return [String, nil]
|
|
15
|
+
attr_accessor :current
|
|
16
|
+
|
|
17
|
+
def initialize(previous:, current:)
|
|
18
|
+
@previous = previous
|
|
19
|
+
@current = current
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class << self
|
|
23
|
+
# @param data [Hash, nil] The raw `user_id` or `parent_user_id` hash.
|
|
24
|
+
# @return [IdChange, nil] `nil` when the key is absent — an id change that did
|
|
25
|
+
# not happen is not a change from nothing to nothing.
|
|
26
|
+
def deserialize(data)
|
|
27
|
+
return if data.nil?
|
|
28
|
+
|
|
29
|
+
new(previous: data["previous"], current: data["current"])
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Whatsapp
|
|
4
|
+
module Webhook
|
|
5
|
+
# A WhatsApp user's business-scoped user ID (BSUID) changed — for example, after
|
|
6
|
+
# they changed their phone number (see the `system` message type's
|
|
7
|
+
# `user_changed_user_id` event) or were re-scoped by Meta.
|
|
8
|
+
# Source: https://developers.facebook.com/documentation/business-messaging/whatsapp/business-scoped-user-ids
|
|
9
|
+
class UserIdUpdate
|
|
10
|
+
# @!attribute [rw] messaging_product
|
|
11
|
+
# @return [String, nil]
|
|
12
|
+
attr_accessor :messaging_product
|
|
13
|
+
|
|
14
|
+
# @!attribute [rw] metadata
|
|
15
|
+
# @return [Metadata, nil]
|
|
16
|
+
attr_accessor :metadata
|
|
17
|
+
|
|
18
|
+
# @!attribute [rw] contacts
|
|
19
|
+
# @return [Array<Contact>]
|
|
20
|
+
attr_accessor :contacts
|
|
21
|
+
|
|
22
|
+
# @!attribute [rw] updates
|
|
23
|
+
# @return [Array<Entry>]
|
|
24
|
+
attr_accessor :updates
|
|
25
|
+
|
|
26
|
+
# @!attribute [r] raw
|
|
27
|
+
# @return [Hash] The untouched `value` hash. A diagnostic escape hatch, not part
|
|
28
|
+
# of the supported API: {#updates} reads `value["user_id_update"]` — entries
|
|
29
|
+
# nested under a repeat of the field's own name, which is what Meta's published
|
|
30
|
+
# example shows — so a key that drifts would otherwise leave `updates` empty
|
|
31
|
+
# with no other trace. Reach for it when a typed accessor is unexpectedly
|
|
32
|
+
# `nil`, log what you find, and treat it as read-only: it is the object
|
|
33
|
+
# `JSON.parse` returned, not a copy.
|
|
34
|
+
attr_reader :raw
|
|
35
|
+
|
|
36
|
+
def initialize(messaging_product:, metadata:, contacts:, updates:, raw: {})
|
|
37
|
+
@messaging_product = messaging_product
|
|
38
|
+
@metadata = metadata
|
|
39
|
+
@contacts = contacts
|
|
40
|
+
@updates = updates
|
|
41
|
+
@raw = raw
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class << self
|
|
45
|
+
# @param data [Hash] The raw `user_id_update` field's `value` hash.
|
|
46
|
+
# @return [UserIdUpdate]
|
|
47
|
+
def deserialize(data)
|
|
48
|
+
data ||= {}
|
|
49
|
+
|
|
50
|
+
new(
|
|
51
|
+
messaging_product: data["messaging_product"],
|
|
52
|
+
metadata: data["metadata"] ? Metadata.deserialize(data["metadata"]) : nil,
|
|
53
|
+
contacts: Array(data["contacts"]).map { |contact_data| Contact.deserialize(contact_data) },
|
|
54
|
+
updates: Array(data["user_id_update"]).map { |entry_data| Entry.deserialize(entry_data) },
|
|
55
|
+
raw: data
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Whatsapp
|
|
4
|
+
module Webhook
|
|
5
|
+
class UserPreferences
|
|
6
|
+
# A single `user_preferences[]` entry: one user changed one preference.
|
|
7
|
+
class Entry
|
|
8
|
+
# @!attribute [rw] wa_id
|
|
9
|
+
# @return [String, nil] The user's phone number, omitted once they adopt a
|
|
10
|
+
# username and it is no longer shareable. Fall back to {#user_id}.
|
|
11
|
+
attr_accessor :wa_id
|
|
12
|
+
|
|
13
|
+
# @!attribute [rw] user_id
|
|
14
|
+
# @return [String, nil] The user's business-scoped user ID (BSUID).
|
|
15
|
+
attr_accessor :user_id
|
|
16
|
+
|
|
17
|
+
# @!attribute [rw] parent_user_id
|
|
18
|
+
# @return [String, nil] The user's parent BSUID, present only when enrolled.
|
|
19
|
+
attr_accessor :parent_user_id
|
|
20
|
+
|
|
21
|
+
# @!attribute [rw] detail
|
|
22
|
+
# @return [String, nil] Human-readable description of the change.
|
|
23
|
+
attr_accessor :detail
|
|
24
|
+
|
|
25
|
+
# @!attribute [rw] category
|
|
26
|
+
# @return [String, nil] The preference category. `"marketing_messages"` is the
|
|
27
|
+
# only value Meta documents; kept a raw String, never validated on the way in,
|
|
28
|
+
# so a category added later still round-trips.
|
|
29
|
+
attr_accessor :category
|
|
30
|
+
|
|
31
|
+
# @!attribute [rw] value
|
|
32
|
+
# @return [String, nil] The preference itself, e.g. `"stop"` or `"resume"`. Note
|
|
33
|
+
# the name: this sits *inside* the change's own `value` hash, so the full path
|
|
34
|
+
# reads `change.value.preferences.first.value`. Meta's naming, not ours.
|
|
35
|
+
attr_accessor :value
|
|
36
|
+
|
|
37
|
+
# @!attribute [rw] timestamp
|
|
38
|
+
# @return [Integer, String, nil] Unix seconds. Meta's example sends a number
|
|
39
|
+
# here, unlike the string timestamps on `messages`, so it is passed through
|
|
40
|
+
# as-is rather than coerced.
|
|
41
|
+
attr_accessor :timestamp
|
|
42
|
+
|
|
43
|
+
def initialize(
|
|
44
|
+
wa_id: nil, user_id: nil, parent_user_id: nil,
|
|
45
|
+
detail: nil, category: nil, value: nil, timestamp: nil
|
|
46
|
+
)
|
|
47
|
+
@wa_id = wa_id
|
|
48
|
+
@user_id = user_id
|
|
49
|
+
@parent_user_id = parent_user_id
|
|
50
|
+
@detail = detail
|
|
51
|
+
@category = category
|
|
52
|
+
@value = value
|
|
53
|
+
@timestamp = timestamp
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
class << self
|
|
57
|
+
# @param data [Hash, nil] A raw `user_preferences[]` entry.
|
|
58
|
+
# @return [Entry]
|
|
59
|
+
def deserialize(data)
|
|
60
|
+
data ||= {}
|
|
61
|
+
|
|
62
|
+
new(
|
|
63
|
+
wa_id: data["wa_id"],
|
|
64
|
+
user_id: data["user_id"],
|
|
65
|
+
parent_user_id: data["parent_user_id"],
|
|
66
|
+
detail: data["detail"],
|
|
67
|
+
category: data["category"],
|
|
68
|
+
value: data["value"],
|
|
69
|
+
timestamp: data["timestamp"]
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|