ruby-whatsapp 0.4.0 → 0.6.1
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 +152 -0
- data/README.md +322 -617
- data/docs/README.md +76 -0
- data/docs/business_phone_number/README.md +243 -0
- data/docs/business_phone_number/account.md +170 -0
- data/docs/business_phone_number/contact_book.md +90 -0
- data/docs/business_phone_number/profile.md +220 -0
- data/docs/business_phone_number/username.md +157 -0
- data/docs/configuration.md +177 -0
- data/docs/errors.md +219 -0
- data/docs/media/README.md +161 -0
- data/docs/message_templates/README.md +206 -0
- data/docs/message_templates/authentication.md +107 -0
- data/docs/message_templates/carousel.md +103 -0
- data/docs/message_templates/components.md +215 -0
- data/docs/message_templates/library.md +98 -0
- data/docs/message_templates/limited_time_offer.md +94 -0
- data/docs/message_templates/responses.md +173 -0
- data/docs/message_templates/standard.md +138 -0
- data/docs/messages/README.md +212 -0
- data/docs/messages/address.md +86 -0
- data/docs/messages/audio.md +43 -0
- data/docs/messages/contacts.md +129 -0
- data/docs/messages/document.md +49 -0
- data/docs/messages/image.md +59 -0
- data/docs/messages/interactive.md +342 -0
- data/docs/messages/location.md +51 -0
- data/docs/messages/location_request.md +59 -0
- data/docs/messages/mark_message_as_read.md +84 -0
- data/docs/messages/reaction.md +59 -0
- data/docs/messages/sticker.md +40 -0
- data/docs/messages/template.md +193 -0
- data/docs/messages/text.md +55 -0
- data/docs/messages/video.md +41 -0
- data/docs/schedule/README.md +183 -0
- data/docs/subscribed_app/README.md +139 -0
- data/docs/webhooks/README.md +217 -0
- data/docs/webhooks/account_alerts.md +44 -0
- data/docs/webhooks/account_review_update.md +29 -0
- data/docs/webhooks/account_update.md +54 -0
- data/docs/webhooks/automatic_events.md +47 -0
- data/docs/webhooks/business_capability_update.md +40 -0
- data/docs/webhooks/business_username_updates.md +55 -0
- data/docs/webhooks/history.md +107 -0
- data/docs/webhooks/message_template_components_update.md +46 -0
- data/docs/webhooks/message_template_quality_update.md +49 -0
- data/docs/webhooks/message_template_status_update.md +55 -0
- data/docs/webhooks/messages.md +520 -0
- data/docs/webhooks/partner_solutions.md +35 -0
- data/docs/webhooks/payment_configuration_update.md +40 -0
- data/docs/webhooks/phone_number_name_update.md +44 -0
- data/docs/webhooks/phone_number_quality_update.md +40 -0
- data/docs/webhooks/security.md +43 -0
- data/docs/webhooks/smb_app_state_sync.md +91 -0
- data/docs/webhooks/smb_message_echoes.md +91 -0
- data/docs/webhooks/template_category_update.md +52 -0
- data/docs/webhooks/user_id_update.md +84 -0
- data/docs/webhooks/user_preferences.md +87 -0
- data/lib/ruby/whatsapp/business_phone_number/account/details.rb +149 -0
- data/lib/ruby/whatsapp/business_phone_number/account/get.rb +53 -0
- data/lib/ruby/whatsapp/business_phone_number/account/transport.rb +30 -0
- data/lib/ruby/whatsapp/business_phone_number/account/update.rb +76 -0
- data/lib/ruby/whatsapp/business_phone_number/account.rb +23 -0
- 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/profile/details.rb +105 -0
- data/lib/ruby/whatsapp/business_phone_number/profile/get.rb +56 -0
- data/lib/ruby/whatsapp/business_phone_number/profile/update.rb +148 -0
- data/lib/ruby/whatsapp/business_phone_number/profile/verticals.rb +54 -0
- data/lib/ruby/whatsapp/business_phone_number/profile.rb +22 -0
- data/lib/ruby/whatsapp/business_phone_number/response.rb +3 -3
- data/lib/ruby/whatsapp/business_phone_number/transport.rb +12 -12
- 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 +20 -4
- 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/message_templates.rb +2 -6
- data/lib/ruby/whatsapp/messages/base.rb +47 -5
- 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/path_building.rb +33 -0
- 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/subscribed_app/transport.rb +6 -5
- 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 +100 -1
|
@@ -2,50 +2,89 @@
|
|
|
2
2
|
|
|
3
3
|
module Whatsapp
|
|
4
4
|
module Webhook
|
|
5
|
-
# Chat history synchronization for onboarded business customers
|
|
5
|
+
# Chat history synchronization for onboarded business customers — one of the fields
|
|
6
|
+
# with a confirmed, JSON-example-backed schema, alongside {Messages},
|
|
7
|
+
# {UserIdUpdate}, {BusinessUsernameUpdates}, and {UserPreferences}.
|
|
6
8
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
9
|
+
# History arrives in chunks. Each `history[]` entry is one {Chunk}, carrying its own
|
|
10
|
+
# sync `metadata` (`phase`/`chunk_order`/`progress`) and the {Thread}s in it — note
|
|
11
|
+
# that the value-level `metadata` is the ordinary business-phone-number
|
|
12
|
+
# {Whatsapp::Webhook::Metadata}, a different object entirely.
|
|
13
|
+
#
|
|
14
|
+
# Meta sends a second variant of this field for a synced media asset, which carries
|
|
15
|
+
# `contacts` and `messages` at the value level instead of inside a thread; both are
|
|
16
|
+
# mapped here, and are empty for a thread-carrying payload.
|
|
17
|
+
# Source: https://developers.facebook.com/documentation/business-messaging/whatsapp/business-scoped-user-ids
|
|
13
18
|
class History
|
|
14
|
-
# @!attribute [rw]
|
|
15
|
-
# @return [
|
|
16
|
-
attr_accessor :
|
|
17
|
-
|
|
18
|
-
# @!attribute [rw]
|
|
19
|
-
# @return [
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
#
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
#
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
19
|
+
# @!attribute [rw] messaging_product
|
|
20
|
+
# @return [String, nil]
|
|
21
|
+
attr_accessor :messaging_product
|
|
22
|
+
|
|
23
|
+
# @!attribute [rw] metadata
|
|
24
|
+
# @return [Metadata, nil] The business phone number this history belongs to — not
|
|
25
|
+
# the per-chunk sync metadata, which lives on {Chunk}.
|
|
26
|
+
attr_accessor :metadata
|
|
27
|
+
|
|
28
|
+
# @!attribute [rw] chunks
|
|
29
|
+
# @return [Array<Chunk>]
|
|
30
|
+
attr_accessor :chunks
|
|
31
|
+
|
|
32
|
+
# @!attribute [rw] contacts
|
|
33
|
+
# @return [Array<Contact>] Populated only by the media-asset variant.
|
|
34
|
+
attr_accessor :contacts
|
|
35
|
+
|
|
36
|
+
# @!attribute [rw] messages
|
|
37
|
+
# @return [Array<Message::Base>] Populated only by the media-asset variant; a
|
|
38
|
+
# thread-carrying payload puts its messages on {Thread#messages} instead.
|
|
39
|
+
attr_accessor :messages
|
|
40
|
+
|
|
41
|
+
# @!attribute [r] raw
|
|
42
|
+
# @return [Hash] The untouched `value` hash. A diagnostic escape hatch, not part of
|
|
43
|
+
# the supported API: this field has two payload variants and the deeper one nests
|
|
44
|
+
# three levels, so a key that drifts would leave a typed accessor empty with no
|
|
45
|
+
# other trace. Treat it as read-only — it is the object `JSON.parse` returned.
|
|
46
|
+
attr_reader :raw
|
|
47
|
+
|
|
48
|
+
def initialize(messaging_product:, metadata:, chunks:, contacts:, messages:, raw: {})
|
|
49
|
+
@messaging_product = messaging_product
|
|
50
|
+
@metadata = metadata
|
|
51
|
+
@chunks = chunks
|
|
52
|
+
@contacts = contacts
|
|
53
|
+
@messages = messages
|
|
54
|
+
@raw = raw
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# A delivery normally carries exactly one chunk, so these three read through to it
|
|
58
|
+
# rather than making every caller reach for `chunks.first`. Check {#chunks} directly
|
|
59
|
+
# if you need to handle a multi-chunk delivery.
|
|
60
|
+
# @return [Integer, nil]
|
|
61
|
+
def phase
|
|
62
|
+
chunks.first&.phase
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @return [Integer, nil]
|
|
66
|
+
def chunk_order
|
|
67
|
+
chunks.first&.chunk_order
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @return [Integer, nil] Percent complete, 0-100.
|
|
71
|
+
def progress
|
|
72
|
+
chunks.first&.progress
|
|
35
73
|
end
|
|
36
74
|
|
|
37
75
|
class << self
|
|
38
|
-
# @param data [Hash] The raw `value` hash.
|
|
76
|
+
# @param data [Hash] The raw `history` field's `value` hash.
|
|
39
77
|
# @return [History]
|
|
40
78
|
def deserialize(data)
|
|
41
79
|
data ||= {}
|
|
42
|
-
metadata = data["metadata"] || {}
|
|
43
80
|
|
|
44
81
|
new(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
82
|
+
messaging_product: data["messaging_product"],
|
|
83
|
+
metadata: data["metadata"] ? Metadata.deserialize(data["metadata"]) : nil,
|
|
84
|
+
chunks: Array(data["history"]).map { |chunk_data| Chunk.deserialize(chunk_data) },
|
|
85
|
+
contacts: Array(data["contacts"]).map { |contact_data| Contact.deserialize(contact_data) },
|
|
86
|
+
messages: Array(data["messages"]).map { |message_data| Message.deserialize(message_data) },
|
|
87
|
+
raw: data
|
|
49
88
|
)
|
|
50
89
|
end
|
|
51
90
|
end
|
|
@@ -7,9 +7,24 @@ module Whatsapp
|
|
|
7
7
|
# wamid, when it arrived, and (optionally) what it's replying to.
|
|
8
8
|
class Base
|
|
9
9
|
# @!attribute [rw] from
|
|
10
|
-
# @return [String, nil]
|
|
10
|
+
# @return [String, nil] Omitted once the sender adopts a username, unless a
|
|
11
|
+
# recent interaction or the contact book still allows sharing the phone number.
|
|
11
12
|
attr_accessor :from
|
|
12
13
|
|
|
14
|
+
# @!attribute [rw] from_user_id
|
|
15
|
+
# @return [String, nil] The sender's business-scoped user ID (BSUID).
|
|
16
|
+
attr_accessor :from_user_id
|
|
17
|
+
|
|
18
|
+
# @!attribute [rw] from_parent_user_id
|
|
19
|
+
# @return [String, nil] The sender's parent BSUID, present only when enrolled.
|
|
20
|
+
attr_accessor :from_parent_user_id
|
|
21
|
+
|
|
22
|
+
# @!attribute [rw] group_id
|
|
23
|
+
# @return [String, nil] Set only when the message was sent in a group chat.
|
|
24
|
+
# Reading groups works; this gem wraps no Groups API, so there is no way to
|
|
25
|
+
# reply into one — the id is for correlation and for filtering group traffic out.
|
|
26
|
+
attr_accessor :group_id
|
|
27
|
+
|
|
13
28
|
# @!attribute [rw] id
|
|
14
29
|
# @return [String, nil] The message's wamid.
|
|
15
30
|
attr_accessor :id
|
|
@@ -30,8 +45,15 @@ module Whatsapp
|
|
|
30
45
|
# @return [Referral, nil]
|
|
31
46
|
attr_accessor :referral
|
|
32
47
|
|
|
33
|
-
def initialize(
|
|
48
|
+
def initialize(
|
|
49
|
+
id:, timestamp:, type:,
|
|
50
|
+
from: nil, from_user_id: nil, from_parent_user_id: nil, group_id: nil,
|
|
51
|
+
context: nil, referral: nil
|
|
52
|
+
)
|
|
34
53
|
@from = from
|
|
54
|
+
@from_user_id = from_user_id
|
|
55
|
+
@from_parent_user_id = from_parent_user_id
|
|
56
|
+
@group_id = group_id
|
|
35
57
|
@id = id
|
|
36
58
|
@timestamp = timestamp
|
|
37
59
|
@type = type
|
|
@@ -47,6 +69,9 @@ module Whatsapp
|
|
|
47
69
|
def common_attributes(data)
|
|
48
70
|
{
|
|
49
71
|
from: data["from"],
|
|
72
|
+
from_user_id: data["from_user_id"],
|
|
73
|
+
from_parent_user_id: data["from_parent_user_id"],
|
|
74
|
+
group_id: data["group_id"],
|
|
50
75
|
id: data["id"],
|
|
51
76
|
timestamp: data["timestamp"],
|
|
52
77
|
type: data["type"],
|
|
@@ -34,7 +34,16 @@ module Whatsapp
|
|
|
34
34
|
# @return [String, nil] Date in YYYY-MM-DD format.
|
|
35
35
|
attr_accessor :birthday
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
# @!attribute [rw] vcard
|
|
38
|
+
# @return [String, nil] The card's raw vCard, set only when `origin` is `"other"`.
|
|
39
|
+
attr_accessor :vcard
|
|
40
|
+
|
|
41
|
+
# @!attribute [rw] origin
|
|
42
|
+
# @return [String, nil] `"contact_request"` (a `REQUEST_CONTACT_INFO` button tap)
|
|
43
|
+
# or `"other"` (shared directly from the chat).
|
|
44
|
+
attr_accessor :origin
|
|
45
|
+
|
|
46
|
+
def initialize(name:, phones:, emails:, addresses:, urls:, org: nil, birthday: nil, vcard: nil, origin: nil)
|
|
38
47
|
@name = name
|
|
39
48
|
@phones = phones
|
|
40
49
|
@emails = emails
|
|
@@ -42,6 +51,8 @@ module Whatsapp
|
|
|
42
51
|
@org = org
|
|
43
52
|
@urls = urls
|
|
44
53
|
@birthday = birthday
|
|
54
|
+
@vcard = vcard
|
|
55
|
+
@origin = origin
|
|
45
56
|
end
|
|
46
57
|
|
|
47
58
|
class << self
|
|
@@ -57,7 +68,9 @@ module Whatsapp
|
|
|
57
68
|
addresses: Array(data["addresses"]).map { |address_data| Address.deserialize(address_data) },
|
|
58
69
|
org: data["org"] ? Org.deserialize(data["org"]) : nil,
|
|
59
70
|
urls: Array(data["urls"]).map { |url_data| Url.deserialize(url_data) },
|
|
60
|
-
birthday: data["birthday"]
|
|
71
|
+
birthday: data["birthday"],
|
|
72
|
+
vcard: data["vcard"],
|
|
73
|
+
origin: data["origin"]
|
|
61
74
|
)
|
|
62
75
|
end
|
|
63
76
|
end
|
|
@@ -7,9 +7,22 @@ module Whatsapp
|
|
|
7
7
|
# swipe, or a tap on an interactive button/list from an earlier message).
|
|
8
8
|
class Context
|
|
9
9
|
# @!attribute [rw] from
|
|
10
|
-
# @return [String, nil]
|
|
10
|
+
# @return [String, nil] The WhatsApp ID of the quoted message's sender, absent
|
|
11
|
+
# once they adopt a username and their phone number is no longer shareable.
|
|
11
12
|
attr_accessor :from
|
|
12
13
|
|
|
14
|
+
# @!attribute [rw] from_user_id
|
|
15
|
+
# @return [String, nil] The quoted sender's BSUID, the fallback identifier once
|
|
16
|
+
# {#from} is gone. **The key name is inferred** by symmetry with
|
|
17
|
+
# {Base#from_user_id} — Meta has not published a `context` example carrying
|
|
18
|
+
# it. Validate against a real payload before depending on it.
|
|
19
|
+
attr_accessor :from_user_id
|
|
20
|
+
|
|
21
|
+
# @!attribute [rw] from_parent_user_id
|
|
22
|
+
# @return [String, nil] The quoted sender's parent BSUID, present only for
|
|
23
|
+
# businesses enrolled in parent BSUIDs. Key name inferred, as above.
|
|
24
|
+
attr_accessor :from_parent_user_id
|
|
25
|
+
|
|
13
26
|
# @!attribute [rw] id
|
|
14
27
|
# @return [String, nil] The wamid of the message being replied to.
|
|
15
28
|
attr_accessor :id
|
|
@@ -22,8 +35,14 @@ module Whatsapp
|
|
|
22
35
|
# @return [Boolean, nil]
|
|
23
36
|
attr_accessor :frequently_forwarded
|
|
24
37
|
|
|
25
|
-
def initialize(
|
|
38
|
+
def initialize(
|
|
39
|
+
id:,
|
|
40
|
+
from: nil, from_user_id: nil, from_parent_user_id: nil,
|
|
41
|
+
forwarded: nil, frequently_forwarded: nil
|
|
42
|
+
)
|
|
26
43
|
@from = from
|
|
44
|
+
@from_user_id = from_user_id
|
|
45
|
+
@from_parent_user_id = from_parent_user_id
|
|
27
46
|
@id = id
|
|
28
47
|
@forwarded = forwarded
|
|
29
48
|
@frequently_forwarded = frequently_forwarded
|
|
@@ -37,6 +56,8 @@ module Whatsapp
|
|
|
37
56
|
|
|
38
57
|
new(
|
|
39
58
|
from: data["from"],
|
|
59
|
+
from_user_id: data["from_user_id"],
|
|
60
|
+
from_parent_user_id: data["from_parent_user_id"],
|
|
40
61
|
id: data["id"],
|
|
41
62
|
forwarded: data["forwarded"],
|
|
42
63
|
frequently_forwarded: data["frequently_forwarded"]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Whatsapp
|
|
4
|
+
module Webhook
|
|
5
|
+
class Message
|
|
6
|
+
# A user edited a message they had sent you. Like {Revoke}, a control message: it
|
|
7
|
+
# names the message being replaced and carries the replacement content.
|
|
8
|
+
#
|
|
9
|
+
# Also arrives through {Whatsapp::Webhook::SmbMessageEchoes} when the business
|
|
10
|
+
# itself edits a message from the WhatsApp Business app.
|
|
11
|
+
# Source: https://developers.facebook.com/documentation/business-messaging/whatsapp/webhooks/reference/messages/edit
|
|
12
|
+
class Edit < Base
|
|
13
|
+
# @!attribute [rw] original_message_id
|
|
14
|
+
# @return [String, nil] The wamid of the message being replaced.
|
|
15
|
+
attr_accessor :original_message_id
|
|
16
|
+
|
|
17
|
+
# @!attribute [rw] message
|
|
18
|
+
# @return [Base, nil] The replacement content, dispatched by its own `type`
|
|
19
|
+
# through {Message.deserialize} like any other message.
|
|
20
|
+
#
|
|
21
|
+
# It carries *content only*. Meta's example gives the nested payload a `type`,
|
|
22
|
+
# its type body, and a `context`, but no `from`/`id`/`timestamp` of its own —
|
|
23
|
+
# those belong to the outer envelope, so the nested message's equivalents read
|
|
24
|
+
# `nil`. Reach for `edit.id` and `edit.from`, not `edit.message.id`.
|
|
25
|
+
attr_accessor :message
|
|
26
|
+
|
|
27
|
+
def initialize(original_message_id: nil, message: nil, **base_attributes)
|
|
28
|
+
super(**base_attributes)
|
|
29
|
+
|
|
30
|
+
@original_message_id = original_message_id
|
|
31
|
+
@message = message
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class << self
|
|
35
|
+
# @param data [Hash] The raw message hash.
|
|
36
|
+
# @return [Edit]
|
|
37
|
+
def deserialize(data)
|
|
38
|
+
edit = data["edit"] || {}
|
|
39
|
+
|
|
40
|
+
new(
|
|
41
|
+
original_message_id: edit["original_message_id"],
|
|
42
|
+
message: edit["message"] ? Message.deserialize(edit["message"]) : nil,
|
|
43
|
+
**common_attributes(data)
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Whatsapp
|
|
4
|
+
module Webhook
|
|
5
|
+
class Message
|
|
6
|
+
# A user deleted a message they had sent you. A control message rather than a
|
|
7
|
+
# content one: it names a message you already received instead of carrying a body.
|
|
8
|
+
#
|
|
9
|
+
# Also arrives through {Whatsapp::Webhook::SmbMessageEchoes} when the business
|
|
10
|
+
# itself revokes a message from the WhatsApp Business app.
|
|
11
|
+
# Source: https://developers.facebook.com/documentation/business-messaging/whatsapp/webhooks/reference/messages/revoke
|
|
12
|
+
class Revoke < Base
|
|
13
|
+
# @!attribute [rw] original_message_id
|
|
14
|
+
# @return [String, nil] The wamid of the message that was deleted.
|
|
15
|
+
attr_accessor :original_message_id
|
|
16
|
+
|
|
17
|
+
def initialize(original_message_id: nil, **base_attributes)
|
|
18
|
+
super(**base_attributes)
|
|
19
|
+
|
|
20
|
+
@original_message_id = original_message_id
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
# @param data [Hash] The raw message hash.
|
|
25
|
+
# @return [Revoke]
|
|
26
|
+
def deserialize(data)
|
|
27
|
+
new(
|
|
28
|
+
original_message_id: data.dig("revoke", "original_message_id"),
|
|
29
|
+
**common_attributes(data)
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -17,16 +17,31 @@ module Whatsapp
|
|
|
17
17
|
# @return [String, nil] The customer's (possibly new) WhatsApp ID.
|
|
18
18
|
attr_accessor :wa_id
|
|
19
19
|
|
|
20
|
+
# @!attribute [rw] user_id
|
|
21
|
+
# @return [String, nil] The customer's new business-scoped user ID (BSUID),
|
|
22
|
+
# set when the change was a `user_changed_user_id` event.
|
|
23
|
+
attr_accessor :user_id
|
|
24
|
+
|
|
25
|
+
# @!attribute [rw] parent_user_id
|
|
26
|
+
# @return [String, nil] The customer's new parent BSUID, present only when enrolled.
|
|
27
|
+
attr_accessor :parent_user_id
|
|
28
|
+
|
|
20
29
|
# @!attribute [rw] change_type
|
|
21
|
-
# @return [String, nil] e.g. `"customer_changed_number"`, `"
|
|
30
|
+
# @return [String, nil] e.g. `"customer_changed_number"`, `"user_changed_user_id"`.
|
|
22
31
|
attr_accessor :change_type
|
|
23
32
|
|
|
24
|
-
def initialize(
|
|
33
|
+
def initialize(
|
|
34
|
+
change_type:,
|
|
35
|
+
body: nil, identity: nil, wa_id: nil, user_id: nil, parent_user_id: nil,
|
|
36
|
+
**base_attributes
|
|
37
|
+
)
|
|
25
38
|
super(**base_attributes)
|
|
26
39
|
|
|
27
40
|
@body = body
|
|
28
41
|
@identity = identity
|
|
29
42
|
@wa_id = wa_id
|
|
43
|
+
@user_id = user_id
|
|
44
|
+
@parent_user_id = parent_user_id
|
|
30
45
|
@change_type = change_type
|
|
31
46
|
end
|
|
32
47
|
|
|
@@ -38,6 +53,8 @@ module Whatsapp
|
|
|
38
53
|
body: data.dig("system", "body"),
|
|
39
54
|
identity: data.dig("system", "identity"),
|
|
40
55
|
wa_id: data.dig("system", "wa_id"),
|
|
56
|
+
user_id: data.dig("system", "user_id"),
|
|
57
|
+
parent_user_id: data.dig("system", "parent_user_id"),
|
|
41
58
|
change_type: data.dig("system", "type"),
|
|
42
59
|
**common_attributes(data)
|
|
43
60
|
)
|
|
@@ -7,9 +7,9 @@ module Whatsapp
|
|
|
7
7
|
# type Meta added after this gem was released, or a message the Cloud API
|
|
8
8
|
# itself couldn't process (carries an `errors[]` array in that case).
|
|
9
9
|
class Unknown < Base
|
|
10
|
-
# @!attribute [
|
|
10
|
+
# @!attribute [r] raw
|
|
11
11
|
# @return [Hash] The full, untyped raw message hash.
|
|
12
|
-
|
|
12
|
+
attr_reader :raw
|
|
13
13
|
|
|
14
14
|
# @!attribute [rw] errors
|
|
15
15
|
# @return [Array<Error>]
|
|
@@ -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
|