ruby-whatsapp 0.3.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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +54 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +15 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +643 -0
- data/Rakefile +12 -0
- data/lib/ruby/whatsapp/client.rb +82 -0
- data/lib/ruby/whatsapp/configuration.rb +79 -0
- data/lib/ruby/whatsapp/instrumentation.rb +34 -0
- data/lib/ruby/whatsapp/media.rb +134 -0
- data/lib/ruby/whatsapp/message_templates/button/base.rb +26 -0
- data/lib/ruby/whatsapp/message_templates/button/copy_code.rb +59 -0
- data/lib/ruby/whatsapp/message_templates/button/otp/supported_app.rb +44 -0
- data/lib/ruby/whatsapp/message_templates/button/otp.rb +138 -0
- data/lib/ruby/whatsapp/message_templates/button/phone_number.rb +45 -0
- data/lib/ruby/whatsapp/message_templates/button/quick_reply.rb +38 -0
- data/lib/ruby/whatsapp/message_templates/button/url.rb +110 -0
- data/lib/ruby/whatsapp/message_templates/button.rb +56 -0
- data/lib/ruby/whatsapp/message_templates/categories.rb +47 -0
- data/lib/ruby/whatsapp/message_templates/component/base.rb +112 -0
- data/lib/ruby/whatsapp/message_templates/component/body.rb +139 -0
- data/lib/ruby/whatsapp/message_templates/component/buttons.rb +95 -0
- data/lib/ruby/whatsapp/message_templates/component/carousel/card.rb +100 -0
- data/lib/ruby/whatsapp/message_templates/component/carousel.rb +71 -0
- data/lib/ruby/whatsapp/message_templates/component/footer.rb +99 -0
- data/lib/ruby/whatsapp/message_templates/component/header.rb +188 -0
- data/lib/ruby/whatsapp/message_templates/component/limited_time_offer.rb +57 -0
- data/lib/ruby/whatsapp/message_templates/component.rb +62 -0
- data/lib/ruby/whatsapp/message_templates/component_set.rb +221 -0
- data/lib/ruby/whatsapp/message_templates/example.rb +148 -0
- data/lib/ruby/whatsapp/message_templates/library_template/body_inputs.rb +81 -0
- data/lib/ruby/whatsapp/message_templates/library_template/button_inputs.rb +146 -0
- data/lib/ruby/whatsapp/message_templates/library_template.rb +114 -0
- data/lib/ruby/whatsapp/message_templates/parameter_formats.rb +49 -0
- data/lib/ruby/whatsapp/message_templates/placeholders.rb +86 -0
- data/lib/ruby/whatsapp/message_templates/response/collection.rb +74 -0
- data/lib/ruby/whatsapp/message_templates/response/created.rb +65 -0
- data/lib/ruby/whatsapp/message_templates/response/node.rb +152 -0
- data/lib/ruby/whatsapp/message_templates/response/paging.rb +39 -0
- data/lib/ruby/whatsapp/message_templates/response/quality_score.rb +49 -0
- data/lib/ruby/whatsapp/message_templates/response/summary.rb +68 -0
- data/lib/ruby/whatsapp/message_templates/response.rb +22 -0
- data/lib/ruby/whatsapp/message_templates/statuses.rb +66 -0
- data/lib/ruby/whatsapp/message_templates/template.rb +180 -0
- data/lib/ruby/whatsapp/message_templates/value_object.rb +62 -0
- data/lib/ruby/whatsapp/message_templates.rb +283 -0
- data/lib/ruby/whatsapp/messages/address.rb +103 -0
- data/lib/ruby/whatsapp/messages/audio.rb +64 -0
- data/lib/ruby/whatsapp/messages/base.rb +46 -0
- data/lib/ruby/whatsapp/messages/contacts/address.rb +84 -0
- data/lib/ruby/whatsapp/messages/contacts/contact.rb +78 -0
- data/lib/ruby/whatsapp/messages/contacts/email.rb +49 -0
- data/lib/ruby/whatsapp/messages/contacts/name.rb +68 -0
- data/lib/ruby/whatsapp/messages/contacts/org.rb +41 -0
- data/lib/ruby/whatsapp/messages/contacts/phone.rb +59 -0
- data/lib/ruby/whatsapp/messages/contacts/url.rb +51 -0
- data/lib/ruby/whatsapp/messages/contacts.rb +37 -0
- data/lib/ruby/whatsapp/messages/document.rb +79 -0
- data/lib/ruby/whatsapp/messages/image.rb +72 -0
- data/lib/ruby/whatsapp/messages/interactive/base.rb +15 -0
- data/lib/ruby/whatsapp/messages/interactive/body.rb +39 -0
- data/lib/ruby/whatsapp/messages/interactive/footer.rb +40 -0
- data/lib/ruby/whatsapp/messages/interactive/header.rb +102 -0
- data/lib/ruby/whatsapp/messages/interactive/list_buttons/section/row.rb +66 -0
- data/lib/ruby/whatsapp/messages/interactive/list_buttons/section.rb +64 -0
- data/lib/ruby/whatsapp/messages/interactive/list_buttons.rb +63 -0
- data/lib/ruby/whatsapp/messages/interactive/media_carousel/card/button/quick_reply.rb +50 -0
- data/lib/ruby/whatsapp/messages/interactive/media_carousel/card/button.rb +47 -0
- data/lib/ruby/whatsapp/messages/interactive/media_carousel/card.rb +78 -0
- data/lib/ruby/whatsapp/messages/interactive/media_carousel.rb +50 -0
- data/lib/ruby/whatsapp/messages/interactive/product_carousel/card.rb +55 -0
- data/lib/ruby/whatsapp/messages/interactive/product_carousel.rb +50 -0
- data/lib/ruby/whatsapp/messages/interactive/reply_buttons/button.rb +59 -0
- data/lib/ruby/whatsapp/messages/interactive/reply_buttons.rb +47 -0
- data/lib/ruby/whatsapp/messages/interactive/url_button.rb +68 -0
- data/lib/ruby/whatsapp/messages/interactive.rb +112 -0
- data/lib/ruby/whatsapp/messages/location.rb +67 -0
- data/lib/ruby/whatsapp/messages/location_request.rb +41 -0
- data/lib/ruby/whatsapp/messages/mark_message_as_read.rb +42 -0
- data/lib/ruby/whatsapp/messages/reaction.rb +61 -0
- data/lib/ruby/whatsapp/messages/response/contacts.rb +36 -0
- data/lib/ruby/whatsapp/messages/response/messages.rb +27 -0
- data/lib/ruby/whatsapp/messages/response.rb +49 -0
- data/lib/ruby/whatsapp/messages/sticker.rb +62 -0
- data/lib/ruby/whatsapp/messages/template/component.rb +67 -0
- data/lib/ruby/whatsapp/messages/template/language.rb +55 -0
- data/lib/ruby/whatsapp/messages/template/parameter.rb +113 -0
- data/lib/ruby/whatsapp/messages/template.rb +62 -0
- data/lib/ruby/whatsapp/messages/text.rb +43 -0
- data/lib/ruby/whatsapp/messages/video.rb +68 -0
- data/lib/ruby/whatsapp/messages.rb +116 -0
- data/lib/ruby/whatsapp/railtie.rb +14 -0
- data/lib/ruby/whatsapp/request_error.rb +5 -0
- data/lib/ruby/whatsapp/response_handling.rb +44 -0
- data/lib/ruby/whatsapp/utils/language_codes.rb +96 -0
- data/lib/ruby/whatsapp/version.rb +5 -0
- data/lib/ruby/whatsapp/webhook/account_alerts.rb +58 -0
- data/lib/ruby/whatsapp/webhook/account_review_update.rb +27 -0
- data/lib/ruby/whatsapp/webhook/account_update/ban_info.rb +33 -0
- data/lib/ruby/whatsapp/webhook/account_update.rb +46 -0
- data/lib/ruby/whatsapp/webhook/automatic_events.rb +43 -0
- data/lib/ruby/whatsapp/webhook/business_capability_update.rb +36 -0
- data/lib/ruby/whatsapp/webhook/change.rb +61 -0
- data/lib/ruby/whatsapp/webhook/contact.rb +36 -0
- data/lib/ruby/whatsapp/webhook/entry.rb +35 -0
- data/lib/ruby/whatsapp/webhook/error.rb +47 -0
- data/lib/ruby/whatsapp/webhook/history.rb +54 -0
- data/lib/ruby/whatsapp/webhook/installer.rb +61 -0
- data/lib/ruby/whatsapp/webhook/message/audio.rb +28 -0
- data/lib/ruby/whatsapp/webhook/message/base.rb +61 -0
- data/lib/ruby/whatsapp/webhook/message/button.rb +33 -0
- data/lib/ruby/whatsapp/webhook/message/contacts/address.rb +68 -0
- data/lib/ruby/whatsapp/webhook/message/contacts/contact.rb +68 -0
- data/lib/ruby/whatsapp/webhook/message/contacts/email.rb +35 -0
- data/lib/ruby/whatsapp/webhook/message/contacts/name.rb +62 -0
- data/lib/ruby/whatsapp/webhook/message/contacts/org.rb +40 -0
- data/lib/ruby/whatsapp/webhook/message/contacts/phone.rb +40 -0
- data/lib/ruby/whatsapp/webhook/message/contacts/url.rb +35 -0
- data/lib/ruby/whatsapp/webhook/message/contacts.rb +31 -0
- data/lib/ruby/whatsapp/webhook/message/context.rb +49 -0
- data/lib/ruby/whatsapp/webhook/message/document.rb +32 -0
- data/lib/ruby/whatsapp/webhook/message/image.rb +18 -0
- data/lib/ruby/whatsapp/webhook/message/interactive.rb +53 -0
- data/lib/ruby/whatsapp/webhook/message/location.rb +49 -0
- data/lib/ruby/whatsapp/webhook/message/media.rb +54 -0
- data/lib/ruby/whatsapp/webhook/message/order/product_item.rb +50 -0
- data/lib/ruby/whatsapp/webhook/message/order.rb +43 -0
- data/lib/ruby/whatsapp/webhook/message/reaction.rb +37 -0
- data/lib/ruby/whatsapp/webhook/message/referral.rb +80 -0
- data/lib/ruby/whatsapp/webhook/message/sticker.rb +32 -0
- data/lib/ruby/whatsapp/webhook/message/system.rb +49 -0
- data/lib/ruby/whatsapp/webhook/message/text.rb +28 -0
- data/lib/ruby/whatsapp/webhook/message/unknown.rb +39 -0
- data/lib/ruby/whatsapp/webhook/message/video.rb +18 -0
- data/lib/ruby/whatsapp/webhook/message.rb +39 -0
- data/lib/ruby/whatsapp/webhook/message_template_components_update.rb +53 -0
- data/lib/ruby/whatsapp/webhook/message_template_quality_update.rb +59 -0
- data/lib/ruby/whatsapp/webhook/message_template_status_update.rb +54 -0
- data/lib/ruby/whatsapp/webhook/messages.rb +55 -0
- data/lib/ruby/whatsapp/webhook/metadata.rb +35 -0
- data/lib/ruby/whatsapp/webhook/notification.rb +36 -0
- data/lib/ruby/whatsapp/webhook/partner_solutions.rb +36 -0
- data/lib/ruby/whatsapp/webhook/payment_configuration_update.rb +51 -0
- data/lib/ruby/whatsapp/webhook/phone_number_name_update.rb +51 -0
- data/lib/ruby/whatsapp/webhook/phone_number_quality_update.rb +45 -0
- data/lib/ruby/whatsapp/webhook/security.rb +41 -0
- data/lib/ruby/whatsapp/webhook/signature.rb +28 -0
- data/lib/ruby/whatsapp/webhook/smb_app_state_sync/state_sync.rb +38 -0
- data/lib/ruby/whatsapp/webhook/smb_app_state_sync.rb +33 -0
- data/lib/ruby/whatsapp/webhook/smb_message_echoes.rb +48 -0
- data/lib/ruby/whatsapp/webhook/status/conversation.rb +42 -0
- data/lib/ruby/whatsapp/webhook/status/pricing.rb +38 -0
- data/lib/ruby/whatsapp/webhook/status.rb +65 -0
- data/lib/ruby/whatsapp/webhook/template_category_update.rb +64 -0
- data/lib/ruby/whatsapp/webhook/templates/webhooks_controller.rb.tt +31 -0
- data/lib/ruby/whatsapp/webhook/unknown_field.rb +37 -0
- data/lib/ruby/whatsapp/webhook/user_preferences.rb +47 -0
- data/lib/ruby/whatsapp/webhook/verification.rb +27 -0
- data/lib/ruby/whatsapp/webhook.rb +8 -0
- data/lib/ruby/whatsapp.rb +49 -0
- data/lib/tasks/whatsapp.rake +10 -0
- data/sig/ruby/whatsapp.rbs +6 -0
- metadata +267 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Whatsapp
|
|
4
|
+
class MessageTemplates
|
|
5
|
+
module Response
|
|
6
|
+
# A page of templates: `{data, paging, summary}`.
|
|
7
|
+
#
|
|
8
|
+
# Enumerable over its templates, so `list.select(&:approved?)` reads naturally
|
|
9
|
+
# without reaching for `.data` first.
|
|
10
|
+
#
|
|
11
|
+
# Paging is exposed rather than followed automatically: the caller decides whether
|
|
12
|
+
# to walk the whole account, and passing {#next_cursor} back as `after:` is one
|
|
13
|
+
# line. Auto-pagination would hide an unbounded number of requests behind a single
|
|
14
|
+
# innocuous-looking call.
|
|
15
|
+
# Source: https://developers.facebook.com/docs/graph-api/reference/whats-app-business-account/message_templates/
|
|
16
|
+
class Collection
|
|
17
|
+
include Enumerable
|
|
18
|
+
|
|
19
|
+
# @!attribute [rw] data
|
|
20
|
+
# @return [Array<Node>]
|
|
21
|
+
attr_accessor :data
|
|
22
|
+
|
|
23
|
+
# @!attribute [rw] paging
|
|
24
|
+
# @return [Paging, nil]
|
|
25
|
+
attr_accessor :paging
|
|
26
|
+
|
|
27
|
+
# @!attribute [rw] summary
|
|
28
|
+
# @return [Summary, nil]
|
|
29
|
+
attr_accessor :summary
|
|
30
|
+
|
|
31
|
+
# @param data [Array<Node>]
|
|
32
|
+
# @param paging [Paging, nil]
|
|
33
|
+
# @param summary [Summary, nil]
|
|
34
|
+
def initialize(data: [], paging: nil, summary: nil)
|
|
35
|
+
@data = data
|
|
36
|
+
@paging = paging
|
|
37
|
+
@summary = summary
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class << self
|
|
41
|
+
# @param response [Hash, nil] The parsed response body.
|
|
42
|
+
# @return [Collection]
|
|
43
|
+
def deserialize(response)
|
|
44
|
+
response ||= {}
|
|
45
|
+
|
|
46
|
+
new(
|
|
47
|
+
data: Array(response["data"]).map { |node| Node.deserialize(node) },
|
|
48
|
+
paging: response["paging"] && Paging.deserialize(response["paging"]),
|
|
49
|
+
summary: response["summary"] && Summary.deserialize(response["summary"])
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @yieldparam template [Node]
|
|
55
|
+
# @return [Enumerator]
|
|
56
|
+
def each(&)
|
|
57
|
+
data.each(&)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# The cursor to pass as `after:` to fetch the next page.
|
|
61
|
+
# @return [String, nil]
|
|
62
|
+
def next_cursor
|
|
63
|
+
paging&.after
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# How many more templates the account can hold.
|
|
67
|
+
# @return [Integer, nil]
|
|
68
|
+
def remaining
|
|
69
|
+
summary&.remaining
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Whatsapp
|
|
4
|
+
class MessageTemplates
|
|
5
|
+
module Response
|
|
6
|
+
# The response to creating a template: `{id, status, category}`.
|
|
7
|
+
#
|
|
8
|
+
# `status` is worth checking rather than assuming: a template built from scratch
|
|
9
|
+
# normally comes back PENDING and takes up to 24 hours to review, but a library
|
|
10
|
+
# clone or an authentication template is usually APPROVED immediately, and a
|
|
11
|
+
# category Meta disagrees with comes back REJECTED right away.
|
|
12
|
+
#
|
|
13
|
+
# `category` is echoed back because Meta may have reassigned it — automatic
|
|
14
|
+
# recategorisation has been the default since 2025-04-09.
|
|
15
|
+
# Source: https://developers.facebook.com/docs/graph-api/reference/whats-app-business-account/message_templates/
|
|
16
|
+
class Created
|
|
17
|
+
# @!attribute [rw] id
|
|
18
|
+
# @return [String, nil]
|
|
19
|
+
attr_accessor :id
|
|
20
|
+
|
|
21
|
+
# @!attribute [rw] status
|
|
22
|
+
# @return [String, nil]
|
|
23
|
+
attr_accessor :status
|
|
24
|
+
|
|
25
|
+
# @!attribute [rw] category
|
|
26
|
+
# @return [String, nil] The category Meta actually assigned.
|
|
27
|
+
attr_accessor :category
|
|
28
|
+
|
|
29
|
+
# @param id [String, nil]
|
|
30
|
+
# @param status [String, nil]
|
|
31
|
+
# @param category [String, nil]
|
|
32
|
+
def initialize(id: nil, status: nil, category: nil)
|
|
33
|
+
@id = id
|
|
34
|
+
@status = status
|
|
35
|
+
@category = category
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class << self
|
|
39
|
+
# @param data [Hash, nil] The parsed response body.
|
|
40
|
+
# @return [Created]
|
|
41
|
+
def deserialize(data)
|
|
42
|
+
data ||= {}
|
|
43
|
+
|
|
44
|
+
new(id: data["id"], status: data["status"], category: data["category"])
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @return [Boolean] Whether the template is already sendable.
|
|
49
|
+
def approved?
|
|
50
|
+
status == Statuses::APPROVED
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @return [Boolean] Whether the template is still in review.
|
|
54
|
+
def pending?
|
|
55
|
+
status == Statuses::PENDING
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @return [Boolean] Whether the template failed review.
|
|
59
|
+
def rejected?
|
|
60
|
+
status == Statuses::REJECTED
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Whatsapp
|
|
4
|
+
class MessageTemplates
|
|
5
|
+
module Response
|
|
6
|
+
# A full template as Meta returns it, from reading one template or from each
|
|
7
|
+
# element of a list.
|
|
8
|
+
#
|
|
9
|
+
# `components` is deliberately left as raw hashes rather than rebuilt into the
|
|
10
|
+
# {Component} classes. Those classes validate a payload being *written*, whereas
|
|
11
|
+
# this is Meta's own echo of it: it carries fields the write side does not model,
|
|
12
|
+
# and running write-side rules over data we did not author would raise on
|
|
13
|
+
# perfectly valid responses. Read it as data; build a new {Template} when you want
|
|
14
|
+
# to change something.
|
|
15
|
+
# Source: https://developers.facebook.com/docs/graph-api/reference/whats-app-business-hsm/
|
|
16
|
+
class Node
|
|
17
|
+
# @!attribute [rw] id
|
|
18
|
+
# @return [String, nil]
|
|
19
|
+
attr_accessor :id
|
|
20
|
+
|
|
21
|
+
# @!attribute [rw] name
|
|
22
|
+
# @return [String, nil]
|
|
23
|
+
attr_accessor :name
|
|
24
|
+
|
|
25
|
+
# @!attribute [rw] status
|
|
26
|
+
# @return [String, nil] One of {Statuses::ALL}.
|
|
27
|
+
attr_accessor :status
|
|
28
|
+
|
|
29
|
+
# @!attribute [rw] category
|
|
30
|
+
# @return [String, nil]
|
|
31
|
+
attr_accessor :category
|
|
32
|
+
|
|
33
|
+
# @!attribute [rw] language
|
|
34
|
+
# @return [String, nil]
|
|
35
|
+
attr_accessor :language
|
|
36
|
+
|
|
37
|
+
# @!attribute [rw] components
|
|
38
|
+
# @return [Array<Hash>] Raw component hashes — see the class comment.
|
|
39
|
+
attr_accessor :components
|
|
40
|
+
|
|
41
|
+
# @!attribute [rw] parameter_format
|
|
42
|
+
# @return [String, nil]
|
|
43
|
+
attr_accessor :parameter_format
|
|
44
|
+
|
|
45
|
+
# @!attribute [rw] sub_category
|
|
46
|
+
# @return [String, nil]
|
|
47
|
+
attr_accessor :sub_category
|
|
48
|
+
|
|
49
|
+
# @!attribute [rw] rejected_reason
|
|
50
|
+
# @return [String, nil] One of {Statuses::RejectedReasons::ALL}.
|
|
51
|
+
attr_accessor :rejected_reason
|
|
52
|
+
|
|
53
|
+
# @!attribute [rw] quality_score
|
|
54
|
+
# @return [QualityScore, nil]
|
|
55
|
+
attr_accessor :quality_score
|
|
56
|
+
|
|
57
|
+
# @!attribute [rw] previous_category
|
|
58
|
+
# @return [String, nil] Set after an automatic recategorisation.
|
|
59
|
+
attr_accessor :previous_category
|
|
60
|
+
|
|
61
|
+
# @!attribute [rw] correct_category
|
|
62
|
+
# @return [String, nil] The category Meta believes this should be.
|
|
63
|
+
attr_accessor :correct_category
|
|
64
|
+
|
|
65
|
+
# @!attribute [rw] message_send_ttl_seconds
|
|
66
|
+
# @return [Integer, nil]
|
|
67
|
+
attr_accessor :message_send_ttl_seconds
|
|
68
|
+
|
|
69
|
+
# @!attribute [rw] cta_url_link_tracking_opted_out
|
|
70
|
+
# @return [Boolean, nil]
|
|
71
|
+
attr_accessor :cta_url_link_tracking_opted_out
|
|
72
|
+
|
|
73
|
+
# @!attribute [rw] library_template_name
|
|
74
|
+
# @return [String, nil] Set when the template was cloned from the library.
|
|
75
|
+
attr_accessor :library_template_name
|
|
76
|
+
|
|
77
|
+
# @param kwargs [Hash] One key per documented node field.
|
|
78
|
+
def initialize(id: nil, name: nil, status: nil, category: nil, language: nil, components: [],
|
|
79
|
+
parameter_format: nil, sub_category: nil, rejected_reason: nil, quality_score: nil,
|
|
80
|
+
previous_category: nil, correct_category: nil, message_send_ttl_seconds: nil,
|
|
81
|
+
cta_url_link_tracking_opted_out: nil, library_template_name: nil)
|
|
82
|
+
@id = id
|
|
83
|
+
@name = name
|
|
84
|
+
@status = status
|
|
85
|
+
@category = category
|
|
86
|
+
@language = language
|
|
87
|
+
@components = components
|
|
88
|
+
@parameter_format = parameter_format
|
|
89
|
+
@sub_category = sub_category
|
|
90
|
+
@rejected_reason = rejected_reason
|
|
91
|
+
@quality_score = quality_score
|
|
92
|
+
@previous_category = previous_category
|
|
93
|
+
@correct_category = correct_category
|
|
94
|
+
@message_send_ttl_seconds = message_send_ttl_seconds
|
|
95
|
+
@cta_url_link_tracking_opted_out = cta_url_link_tracking_opted_out
|
|
96
|
+
@library_template_name = library_template_name
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
class << self
|
|
100
|
+
# @param data [Hash, nil] The parsed response body, or one element of a list.
|
|
101
|
+
# @return [Node]
|
|
102
|
+
def deserialize(data)
|
|
103
|
+
data ||= {}
|
|
104
|
+
|
|
105
|
+
new(
|
|
106
|
+
id: data["id"],
|
|
107
|
+
name: data["name"],
|
|
108
|
+
status: data["status"],
|
|
109
|
+
category: data["category"],
|
|
110
|
+
language: data["language"],
|
|
111
|
+
components: Array(data["components"]),
|
|
112
|
+
parameter_format: data["parameter_format"],
|
|
113
|
+
sub_category: data["sub_category"],
|
|
114
|
+
rejected_reason: data["rejected_reason"],
|
|
115
|
+
quality_score: data["quality_score"] && QualityScore.deserialize(data["quality_score"]),
|
|
116
|
+
previous_category: data["previous_category"],
|
|
117
|
+
correct_category: data["correct_category"],
|
|
118
|
+
message_send_ttl_seconds: data["message_send_ttl_seconds"],
|
|
119
|
+
cta_url_link_tracking_opted_out: data["cta_url_link_tracking_opted_out"],
|
|
120
|
+
library_template_name: data["library_template_name"]
|
|
121
|
+
)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# @return [Boolean] Whether the template can currently be sent.
|
|
126
|
+
def approved?
|
|
127
|
+
status == Statuses::APPROVED
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# @return [Boolean]
|
|
131
|
+
def rejected?
|
|
132
|
+
status == Statuses::REJECTED
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# @return [Boolean]
|
|
136
|
+
def paused?
|
|
137
|
+
status == Statuses::PAUSED
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Whether {MessageTemplates#update} would be accepted for this template.
|
|
141
|
+
#
|
|
142
|
+
# Status is the only part that can be checked locally. Approved templates are
|
|
143
|
+
# also rate limited to 10 edits per 30 days and 1 per 24 hours, which only the
|
|
144
|
+
# API can adjudicate.
|
|
145
|
+
# @return [Boolean]
|
|
146
|
+
def editable?
|
|
147
|
+
Statuses::EDITABLE.include?(status)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Whatsapp
|
|
4
|
+
class MessageTemplates
|
|
5
|
+
module Response
|
|
6
|
+
# Graph API cursor pagination for a template list.
|
|
7
|
+
#
|
|
8
|
+
# Flattens the nested `paging.cursors` object, since the intermediate level
|
|
9
|
+
# carries no information of its own.
|
|
10
|
+
# Source: https://developers.facebook.com/docs/graph-api/reference/whats-app-business-account/message_templates/
|
|
11
|
+
class Paging
|
|
12
|
+
# @!attribute [rw] before
|
|
13
|
+
# @return [String, nil] Cursor for the previous page.
|
|
14
|
+
attr_accessor :before
|
|
15
|
+
|
|
16
|
+
# @!attribute [rw] after
|
|
17
|
+
# @return [String, nil] Cursor for the next page.
|
|
18
|
+
attr_accessor :after
|
|
19
|
+
|
|
20
|
+
# @param before [String, nil]
|
|
21
|
+
# @param after [String, nil]
|
|
22
|
+
def initialize(before: nil, after: nil)
|
|
23
|
+
@before = before
|
|
24
|
+
@after = after
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class << self
|
|
28
|
+
# @param data [Hash, nil] The raw `paging` object.
|
|
29
|
+
# @return [Paging]
|
|
30
|
+
def deserialize(data)
|
|
31
|
+
cursors = (data || {})["cursors"] || {}
|
|
32
|
+
|
|
33
|
+
new(before: cursors["before"], after: cursors["after"])
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Whatsapp
|
|
4
|
+
class MessageTemplates
|
|
5
|
+
module Response
|
|
6
|
+
# A template's quality assessment, derived from usage, recipient feedback and
|
|
7
|
+
# engagement. A falling score is what precedes a PAUSED and then DISABLED status.
|
|
8
|
+
#
|
|
9
|
+
# Best-effort schema: Meta names the shape (`WhatsAppBusinessHSMQualityScoreShape`)
|
|
10
|
+
# in the Graph API reference but does not publish its sub-fields. The three mapped
|
|
11
|
+
# here are what live responses have been observed to carry. Validate against a real
|
|
12
|
+
# response before relying on them, as with the best-effort webhook classes in
|
|
13
|
+
# `lib/ruby/whatsapp/webhook/CLAUDE.md`.
|
|
14
|
+
# Source: https://developers.facebook.com/docs/graph-api/reference/whats-app-business-hsm/
|
|
15
|
+
class QualityScore
|
|
16
|
+
# @!attribute [rw] score
|
|
17
|
+
# @return [String, nil] One of {Statuses::QualityScores::ALL}.
|
|
18
|
+
attr_accessor :score
|
|
19
|
+
|
|
20
|
+
# @!attribute [rw] date
|
|
21
|
+
# @return [Integer, String, nil] When the score was last assessed.
|
|
22
|
+
attr_accessor :date
|
|
23
|
+
|
|
24
|
+
# @!attribute [rw] reasons
|
|
25
|
+
# @return [Array]
|
|
26
|
+
attr_accessor :reasons
|
|
27
|
+
|
|
28
|
+
# @param score [String, nil]
|
|
29
|
+
# @param date [Integer, String, nil]
|
|
30
|
+
# @param reasons [Array, nil]
|
|
31
|
+
def initialize(score: nil, date: nil, reasons: [])
|
|
32
|
+
@score = score
|
|
33
|
+
@date = date
|
|
34
|
+
@reasons = reasons
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class << self
|
|
38
|
+
# @param data [Hash, nil]
|
|
39
|
+
# @return [QualityScore]
|
|
40
|
+
def deserialize(data)
|
|
41
|
+
data ||= {}
|
|
42
|
+
|
|
43
|
+
new(score: data["score"], date: data["date"], reasons: Array(data["reasons"]))
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Whatsapp
|
|
4
|
+
class MessageTemplates
|
|
5
|
+
module Response
|
|
6
|
+
# The `summary` block of a template list.
|
|
7
|
+
#
|
|
8
|
+
# Worth reading rather than ignoring: `message_template_count` against
|
|
9
|
+
# `message_template_limit` is the only way to see how close a WABA is to its
|
|
10
|
+
# template cap — 250 for an unverified business portfolio, up to 6,000 for a
|
|
11
|
+
# verified one with an approved display name. Hitting it turns every subsequent
|
|
12
|
+
# create into an error.
|
|
13
|
+
# Source: https://developers.facebook.com/docs/graph-api/reference/whats-app-business-account/message_templates/
|
|
14
|
+
class Summary
|
|
15
|
+
# @!attribute [rw] total_count
|
|
16
|
+
# @return [Integer, nil]
|
|
17
|
+
attr_accessor :total_count
|
|
18
|
+
|
|
19
|
+
# @!attribute [rw] message_template_count
|
|
20
|
+
# @return [Integer, nil] Templates currently on the account.
|
|
21
|
+
attr_accessor :message_template_count
|
|
22
|
+
|
|
23
|
+
# @!attribute [rw] message_template_limit
|
|
24
|
+
# @return [Integer, nil] The account's cap.
|
|
25
|
+
attr_accessor :message_template_limit
|
|
26
|
+
|
|
27
|
+
# @!attribute [rw] are_translations_complete
|
|
28
|
+
# @return [Boolean, nil]
|
|
29
|
+
attr_accessor :are_translations_complete
|
|
30
|
+
|
|
31
|
+
# @param total_count [Integer, nil]
|
|
32
|
+
# @param message_template_count [Integer, nil]
|
|
33
|
+
# @param message_template_limit [Integer, nil]
|
|
34
|
+
# @param are_translations_complete [Boolean, nil]
|
|
35
|
+
def initialize(total_count: nil, message_template_count: nil, message_template_limit: nil,
|
|
36
|
+
are_translations_complete: nil)
|
|
37
|
+
@total_count = total_count
|
|
38
|
+
@message_template_count = message_template_count
|
|
39
|
+
@message_template_limit = message_template_limit
|
|
40
|
+
@are_translations_complete = are_translations_complete
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class << self
|
|
44
|
+
# @param data [Hash, nil] The raw `summary` object.
|
|
45
|
+
# @return [Summary]
|
|
46
|
+
def deserialize(data)
|
|
47
|
+
data ||= {}
|
|
48
|
+
|
|
49
|
+
new(
|
|
50
|
+
total_count: data["total_count"],
|
|
51
|
+
message_template_count: data["message_template_count"],
|
|
52
|
+
message_template_limit: data["message_template_limit"],
|
|
53
|
+
are_translations_complete: data["are_translations_complete"]
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# How many more templates the account can hold.
|
|
59
|
+
# @return [Integer, nil] nil when either figure is missing.
|
|
60
|
+
def remaining
|
|
61
|
+
return if message_template_limit.nil? || message_template_count.nil?
|
|
62
|
+
|
|
63
|
+
message_template_limit - message_template_count
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Whatsapp
|
|
4
|
+
class MessageTemplates
|
|
5
|
+
# Deserializers for the responses of the template-management edge.
|
|
6
|
+
#
|
|
7
|
+
# Unlike {Whatsapp::Messages::Response}, which parses one shape, this edge answers
|
|
8
|
+
# in four different ones depending on the operation — hence a namespace of small
|
|
9
|
+
# classes rather than a single one with optional fields:
|
|
10
|
+
#
|
|
11
|
+
# {Created} `{id, status, category}` create, create_from_library, upsert
|
|
12
|
+
# {Node} the full template object find, and each element of a list
|
|
13
|
+
# {Collection} `{data, paging, summary}` list
|
|
14
|
+
# — `{success: true}` update and delete, returned as a
|
|
15
|
+
# plain boolean, as {Media#delete} does
|
|
16
|
+
#
|
|
17
|
+
# Every class follows the gem's `.deserialize(data)` convention and tolerates a nil
|
|
18
|
+
# or partial payload, so a field Meta stops sending cannot raise.
|
|
19
|
+
module Response
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Whatsapp
|
|
4
|
+
class MessageTemplates
|
|
5
|
+
# The review and health states a template moves through.
|
|
6
|
+
#
|
|
7
|
+
# Only APPROVED templates can be sent. Status changes arrive asynchronously via the
|
|
8
|
+
# `message_template_status_update` webhook, already modelled by
|
|
9
|
+
# {Whatsapp::Webhook::MessageTemplateStatusUpdate} — polling {MessageTemplates#find}
|
|
10
|
+
# is the fallback, not the intended path.
|
|
11
|
+
# Source: https://developers.facebook.com/docs/graph-api/reference/whats-app-business-hsm/
|
|
12
|
+
module Statuses
|
|
13
|
+
# Passed review and sendable.
|
|
14
|
+
APPROVED = "APPROVED"
|
|
15
|
+
# In review; can take up to 24 hours.
|
|
16
|
+
PENDING = "PENDING"
|
|
17
|
+
# Failed review or violated policy. See `rejected_reason`.
|
|
18
|
+
REJECTED = "REJECTED"
|
|
19
|
+
# Suspended after recurring negative feedback or low read rates.
|
|
20
|
+
PAUSED = "PAUSED"
|
|
21
|
+
# Permanently disabled. Cannot be sent, and cannot be deleted.
|
|
22
|
+
DISABLED = "DISABLED"
|
|
23
|
+
# An appeal is in progress.
|
|
24
|
+
IN_APPEAL = "IN_APPEAL"
|
|
25
|
+
# Deleted, with a 30-day window for messages already in flight.
|
|
26
|
+
PENDING_DELETION = "PENDING_DELETION"
|
|
27
|
+
DELETED = "DELETED"
|
|
28
|
+
# Over a limit.
|
|
29
|
+
LIMIT_EXCEEDED = "LIMIT_EXCEEDED"
|
|
30
|
+
# Inactive for 12 months or more.
|
|
31
|
+
ARCHIVED = "ARCHIVED"
|
|
32
|
+
|
|
33
|
+
ALL = [
|
|
34
|
+
APPROVED, PENDING, REJECTED, PAUSED, DISABLED,
|
|
35
|
+
IN_APPEAL, PENDING_DELETION, DELETED, LIMIT_EXCEEDED, ARCHIVED,
|
|
36
|
+
].freeze
|
|
37
|
+
|
|
38
|
+
# The only statuses Meta allows a template to be edited in. Approved templates
|
|
39
|
+
# additionally carry rate limits (10 edits per 30 days, and 1 per 24 hours) that
|
|
40
|
+
# cannot be checked locally.
|
|
41
|
+
EDITABLE = [APPROVED, REJECTED, PAUSED].freeze
|
|
42
|
+
|
|
43
|
+
# Reasons Meta gives for a REJECTED status.
|
|
44
|
+
module RejectedReasons
|
|
45
|
+
NONE = "NONE"
|
|
46
|
+
ABUSIVE_CONTENT = "ABUSIVE_CONTENT"
|
|
47
|
+
INVALID_FORMAT = "INVALID_FORMAT"
|
|
48
|
+
PROMOTIONAL = "PROMOTIONAL"
|
|
49
|
+
TAG_CONTENT_MISMATCH = "TAG_CONTENT_MISMATCH"
|
|
50
|
+
SCAM = "SCAM"
|
|
51
|
+
|
|
52
|
+
ALL = [NONE, ABUSIVE_CONTENT, INVALID_FORMAT, PROMOTIONAL, TAG_CONTENT_MISMATCH, SCAM].freeze
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Quality buckets derived from recipient feedback and engagement.
|
|
56
|
+
module QualityScores
|
|
57
|
+
GREEN = "GREEN"
|
|
58
|
+
YELLOW = "YELLOW"
|
|
59
|
+
RED = "RED"
|
|
60
|
+
UNKNOWN = "UNKNOWN"
|
|
61
|
+
|
|
62
|
+
ALL = [GREEN, YELLOW, RED, UNKNOWN].freeze
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|