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.
Files changed (166) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +54 -0
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +15 -0
  6. data/CODE_OF_CONDUCT.md +132 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +643 -0
  9. data/Rakefile +12 -0
  10. data/lib/ruby/whatsapp/client.rb +82 -0
  11. data/lib/ruby/whatsapp/configuration.rb +79 -0
  12. data/lib/ruby/whatsapp/instrumentation.rb +34 -0
  13. data/lib/ruby/whatsapp/media.rb +134 -0
  14. data/lib/ruby/whatsapp/message_templates/button/base.rb +26 -0
  15. data/lib/ruby/whatsapp/message_templates/button/copy_code.rb +59 -0
  16. data/lib/ruby/whatsapp/message_templates/button/otp/supported_app.rb +44 -0
  17. data/lib/ruby/whatsapp/message_templates/button/otp.rb +138 -0
  18. data/lib/ruby/whatsapp/message_templates/button/phone_number.rb +45 -0
  19. data/lib/ruby/whatsapp/message_templates/button/quick_reply.rb +38 -0
  20. data/lib/ruby/whatsapp/message_templates/button/url.rb +110 -0
  21. data/lib/ruby/whatsapp/message_templates/button.rb +56 -0
  22. data/lib/ruby/whatsapp/message_templates/categories.rb +47 -0
  23. data/lib/ruby/whatsapp/message_templates/component/base.rb +112 -0
  24. data/lib/ruby/whatsapp/message_templates/component/body.rb +139 -0
  25. data/lib/ruby/whatsapp/message_templates/component/buttons.rb +95 -0
  26. data/lib/ruby/whatsapp/message_templates/component/carousel/card.rb +100 -0
  27. data/lib/ruby/whatsapp/message_templates/component/carousel.rb +71 -0
  28. data/lib/ruby/whatsapp/message_templates/component/footer.rb +99 -0
  29. data/lib/ruby/whatsapp/message_templates/component/header.rb +188 -0
  30. data/lib/ruby/whatsapp/message_templates/component/limited_time_offer.rb +57 -0
  31. data/lib/ruby/whatsapp/message_templates/component.rb +62 -0
  32. data/lib/ruby/whatsapp/message_templates/component_set.rb +221 -0
  33. data/lib/ruby/whatsapp/message_templates/example.rb +148 -0
  34. data/lib/ruby/whatsapp/message_templates/library_template/body_inputs.rb +81 -0
  35. data/lib/ruby/whatsapp/message_templates/library_template/button_inputs.rb +146 -0
  36. data/lib/ruby/whatsapp/message_templates/library_template.rb +114 -0
  37. data/lib/ruby/whatsapp/message_templates/parameter_formats.rb +49 -0
  38. data/lib/ruby/whatsapp/message_templates/placeholders.rb +86 -0
  39. data/lib/ruby/whatsapp/message_templates/response/collection.rb +74 -0
  40. data/lib/ruby/whatsapp/message_templates/response/created.rb +65 -0
  41. data/lib/ruby/whatsapp/message_templates/response/node.rb +152 -0
  42. data/lib/ruby/whatsapp/message_templates/response/paging.rb +39 -0
  43. data/lib/ruby/whatsapp/message_templates/response/quality_score.rb +49 -0
  44. data/lib/ruby/whatsapp/message_templates/response/summary.rb +68 -0
  45. data/lib/ruby/whatsapp/message_templates/response.rb +22 -0
  46. data/lib/ruby/whatsapp/message_templates/statuses.rb +66 -0
  47. data/lib/ruby/whatsapp/message_templates/template.rb +180 -0
  48. data/lib/ruby/whatsapp/message_templates/value_object.rb +62 -0
  49. data/lib/ruby/whatsapp/message_templates.rb +283 -0
  50. data/lib/ruby/whatsapp/messages/address.rb +103 -0
  51. data/lib/ruby/whatsapp/messages/audio.rb +64 -0
  52. data/lib/ruby/whatsapp/messages/base.rb +46 -0
  53. data/lib/ruby/whatsapp/messages/contacts/address.rb +84 -0
  54. data/lib/ruby/whatsapp/messages/contacts/contact.rb +78 -0
  55. data/lib/ruby/whatsapp/messages/contacts/email.rb +49 -0
  56. data/lib/ruby/whatsapp/messages/contacts/name.rb +68 -0
  57. data/lib/ruby/whatsapp/messages/contacts/org.rb +41 -0
  58. data/lib/ruby/whatsapp/messages/contacts/phone.rb +59 -0
  59. data/lib/ruby/whatsapp/messages/contacts/url.rb +51 -0
  60. data/lib/ruby/whatsapp/messages/contacts.rb +37 -0
  61. data/lib/ruby/whatsapp/messages/document.rb +79 -0
  62. data/lib/ruby/whatsapp/messages/image.rb +72 -0
  63. data/lib/ruby/whatsapp/messages/interactive/base.rb +15 -0
  64. data/lib/ruby/whatsapp/messages/interactive/body.rb +39 -0
  65. data/lib/ruby/whatsapp/messages/interactive/footer.rb +40 -0
  66. data/lib/ruby/whatsapp/messages/interactive/header.rb +102 -0
  67. data/lib/ruby/whatsapp/messages/interactive/list_buttons/section/row.rb +66 -0
  68. data/lib/ruby/whatsapp/messages/interactive/list_buttons/section.rb +64 -0
  69. data/lib/ruby/whatsapp/messages/interactive/list_buttons.rb +63 -0
  70. data/lib/ruby/whatsapp/messages/interactive/media_carousel/card/button/quick_reply.rb +50 -0
  71. data/lib/ruby/whatsapp/messages/interactive/media_carousel/card/button.rb +47 -0
  72. data/lib/ruby/whatsapp/messages/interactive/media_carousel/card.rb +78 -0
  73. data/lib/ruby/whatsapp/messages/interactive/media_carousel.rb +50 -0
  74. data/lib/ruby/whatsapp/messages/interactive/product_carousel/card.rb +55 -0
  75. data/lib/ruby/whatsapp/messages/interactive/product_carousel.rb +50 -0
  76. data/lib/ruby/whatsapp/messages/interactive/reply_buttons/button.rb +59 -0
  77. data/lib/ruby/whatsapp/messages/interactive/reply_buttons.rb +47 -0
  78. data/lib/ruby/whatsapp/messages/interactive/url_button.rb +68 -0
  79. data/lib/ruby/whatsapp/messages/interactive.rb +112 -0
  80. data/lib/ruby/whatsapp/messages/location.rb +67 -0
  81. data/lib/ruby/whatsapp/messages/location_request.rb +41 -0
  82. data/lib/ruby/whatsapp/messages/mark_message_as_read.rb +42 -0
  83. data/lib/ruby/whatsapp/messages/reaction.rb +61 -0
  84. data/lib/ruby/whatsapp/messages/response/contacts.rb +36 -0
  85. data/lib/ruby/whatsapp/messages/response/messages.rb +27 -0
  86. data/lib/ruby/whatsapp/messages/response.rb +49 -0
  87. data/lib/ruby/whatsapp/messages/sticker.rb +62 -0
  88. data/lib/ruby/whatsapp/messages/template/component.rb +67 -0
  89. data/lib/ruby/whatsapp/messages/template/language.rb +55 -0
  90. data/lib/ruby/whatsapp/messages/template/parameter.rb +113 -0
  91. data/lib/ruby/whatsapp/messages/template.rb +62 -0
  92. data/lib/ruby/whatsapp/messages/text.rb +43 -0
  93. data/lib/ruby/whatsapp/messages/video.rb +68 -0
  94. data/lib/ruby/whatsapp/messages.rb +116 -0
  95. data/lib/ruby/whatsapp/railtie.rb +14 -0
  96. data/lib/ruby/whatsapp/request_error.rb +5 -0
  97. data/lib/ruby/whatsapp/response_handling.rb +44 -0
  98. data/lib/ruby/whatsapp/utils/language_codes.rb +96 -0
  99. data/lib/ruby/whatsapp/version.rb +5 -0
  100. data/lib/ruby/whatsapp/webhook/account_alerts.rb +58 -0
  101. data/lib/ruby/whatsapp/webhook/account_review_update.rb +27 -0
  102. data/lib/ruby/whatsapp/webhook/account_update/ban_info.rb +33 -0
  103. data/lib/ruby/whatsapp/webhook/account_update.rb +46 -0
  104. data/lib/ruby/whatsapp/webhook/automatic_events.rb +43 -0
  105. data/lib/ruby/whatsapp/webhook/business_capability_update.rb +36 -0
  106. data/lib/ruby/whatsapp/webhook/change.rb +61 -0
  107. data/lib/ruby/whatsapp/webhook/contact.rb +36 -0
  108. data/lib/ruby/whatsapp/webhook/entry.rb +35 -0
  109. data/lib/ruby/whatsapp/webhook/error.rb +47 -0
  110. data/lib/ruby/whatsapp/webhook/history.rb +54 -0
  111. data/lib/ruby/whatsapp/webhook/installer.rb +61 -0
  112. data/lib/ruby/whatsapp/webhook/message/audio.rb +28 -0
  113. data/lib/ruby/whatsapp/webhook/message/base.rb +61 -0
  114. data/lib/ruby/whatsapp/webhook/message/button.rb +33 -0
  115. data/lib/ruby/whatsapp/webhook/message/contacts/address.rb +68 -0
  116. data/lib/ruby/whatsapp/webhook/message/contacts/contact.rb +68 -0
  117. data/lib/ruby/whatsapp/webhook/message/contacts/email.rb +35 -0
  118. data/lib/ruby/whatsapp/webhook/message/contacts/name.rb +62 -0
  119. data/lib/ruby/whatsapp/webhook/message/contacts/org.rb +40 -0
  120. data/lib/ruby/whatsapp/webhook/message/contacts/phone.rb +40 -0
  121. data/lib/ruby/whatsapp/webhook/message/contacts/url.rb +35 -0
  122. data/lib/ruby/whatsapp/webhook/message/contacts.rb +31 -0
  123. data/lib/ruby/whatsapp/webhook/message/context.rb +49 -0
  124. data/lib/ruby/whatsapp/webhook/message/document.rb +32 -0
  125. data/lib/ruby/whatsapp/webhook/message/image.rb +18 -0
  126. data/lib/ruby/whatsapp/webhook/message/interactive.rb +53 -0
  127. data/lib/ruby/whatsapp/webhook/message/location.rb +49 -0
  128. data/lib/ruby/whatsapp/webhook/message/media.rb +54 -0
  129. data/lib/ruby/whatsapp/webhook/message/order/product_item.rb +50 -0
  130. data/lib/ruby/whatsapp/webhook/message/order.rb +43 -0
  131. data/lib/ruby/whatsapp/webhook/message/reaction.rb +37 -0
  132. data/lib/ruby/whatsapp/webhook/message/referral.rb +80 -0
  133. data/lib/ruby/whatsapp/webhook/message/sticker.rb +32 -0
  134. data/lib/ruby/whatsapp/webhook/message/system.rb +49 -0
  135. data/lib/ruby/whatsapp/webhook/message/text.rb +28 -0
  136. data/lib/ruby/whatsapp/webhook/message/unknown.rb +39 -0
  137. data/lib/ruby/whatsapp/webhook/message/video.rb +18 -0
  138. data/lib/ruby/whatsapp/webhook/message.rb +39 -0
  139. data/lib/ruby/whatsapp/webhook/message_template_components_update.rb +53 -0
  140. data/lib/ruby/whatsapp/webhook/message_template_quality_update.rb +59 -0
  141. data/lib/ruby/whatsapp/webhook/message_template_status_update.rb +54 -0
  142. data/lib/ruby/whatsapp/webhook/messages.rb +55 -0
  143. data/lib/ruby/whatsapp/webhook/metadata.rb +35 -0
  144. data/lib/ruby/whatsapp/webhook/notification.rb +36 -0
  145. data/lib/ruby/whatsapp/webhook/partner_solutions.rb +36 -0
  146. data/lib/ruby/whatsapp/webhook/payment_configuration_update.rb +51 -0
  147. data/lib/ruby/whatsapp/webhook/phone_number_name_update.rb +51 -0
  148. data/lib/ruby/whatsapp/webhook/phone_number_quality_update.rb +45 -0
  149. data/lib/ruby/whatsapp/webhook/security.rb +41 -0
  150. data/lib/ruby/whatsapp/webhook/signature.rb +28 -0
  151. data/lib/ruby/whatsapp/webhook/smb_app_state_sync/state_sync.rb +38 -0
  152. data/lib/ruby/whatsapp/webhook/smb_app_state_sync.rb +33 -0
  153. data/lib/ruby/whatsapp/webhook/smb_message_echoes.rb +48 -0
  154. data/lib/ruby/whatsapp/webhook/status/conversation.rb +42 -0
  155. data/lib/ruby/whatsapp/webhook/status/pricing.rb +38 -0
  156. data/lib/ruby/whatsapp/webhook/status.rb +65 -0
  157. data/lib/ruby/whatsapp/webhook/template_category_update.rb +64 -0
  158. data/lib/ruby/whatsapp/webhook/templates/webhooks_controller.rb.tt +31 -0
  159. data/lib/ruby/whatsapp/webhook/unknown_field.rb +37 -0
  160. data/lib/ruby/whatsapp/webhook/user_preferences.rb +47 -0
  161. data/lib/ruby/whatsapp/webhook/verification.rb +27 -0
  162. data/lib/ruby/whatsapp/webhook.rb +8 -0
  163. data/lib/ruby/whatsapp.rb +49 -0
  164. data/lib/tasks/whatsapp.rake +10 -0
  165. data/sig/ruby/whatsapp.rbs +6 -0
  166. 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