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,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Whatsapp
4
+ class Messages
5
+ class Interactive
6
+ class ProductCarousel < Base
7
+ module Defaults
8
+ MIN_CARDS_COUNT = 2
9
+ MAX_CARDS_COUNT = 10
10
+ end
11
+
12
+ # @!attribute [rw] cards
13
+ # @return [Array<Hash>]
14
+ attr_accessor :cards
15
+
16
+ validates :cards, presence: true, length: { minimum: Defaults::MIN_CARDS_COUNT,
17
+ maximum: Defaults::MAX_CARDS_COUNT, }
18
+
19
+ # @param cards [Array<Hash>] The cards of the product carousel.
20
+ def initialize(cards:)
21
+ @cards = cards
22
+
23
+ validate!
24
+ end
25
+
26
+ class << self
27
+ # @return [Hash] Serialized representation of the ProductCarousel.
28
+ def serialize(cards:)
29
+ new(cards:).serialize
30
+ end
31
+ end
32
+
33
+ # @return [Hash] Serialized representation of the ProductCarousel.
34
+ def serialize
35
+ {
36
+ cards: serialized_cards,
37
+ }
38
+ end
39
+
40
+ private
41
+
42
+ # @return [Array<Hash>] Serialized cards.
43
+ # @raise [ActiveModel::ValidationError] if validation fails.
44
+ def serialized_cards
45
+ cards.map.with_index { |card, index| Card.serialize(card_index: index, **card) }
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Whatsapp
4
+ class Messages
5
+ class Interactive
6
+ class ReplyButtons
7
+ class Button
8
+ include ActiveModel::Validations
9
+
10
+ module Defaults
11
+ TYPE = "reply"
12
+ MAX_ID_LENGTH = 256
13
+ MAX_TITLE_LENGTH = 20
14
+ end
15
+
16
+ # @!attribute [rw] id
17
+ # @return [String]
18
+ attr_accessor :id
19
+
20
+ # @!attribute [rw] title
21
+ # @return [String]
22
+ attr_accessor :title
23
+
24
+ validates :id, presence: true, length: { maximum: Defaults::MAX_ID_LENGTH }
25
+ validates :title, presence: true, length: { maximum: Defaults::MAX_TITLE_LENGTH }
26
+
27
+ # @param id [String] The ID of the button.
28
+ # @param title [String] The title of the button.
29
+ # @raise [ActiveModel::ValidationError] if validation fails.
30
+ def initialize(id:, title:)
31
+ @id = id
32
+ @title = title
33
+
34
+ validate!
35
+ end
36
+
37
+ class << self
38
+ # @return [Hash] Serialized representation of the Button.
39
+ # @raise [ActiveModel::ValidationError] if validation fails.
40
+ def serialize(id:, title:)
41
+ new(id:, title:).serialize
42
+ end
43
+ end
44
+
45
+ # @return [Hash] Serialized representation of the Button.
46
+ def serialize
47
+ {
48
+ type: Defaults::TYPE,
49
+ reply: {
50
+ id:,
51
+ title:,
52
+ },
53
+ }
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Whatsapp
4
+ class Messages
5
+ class Interactive
6
+ class ReplyButtons < Base
7
+ module Defaults
8
+ MAX_BUTTONS = 3
9
+ end
10
+
11
+ # @!attribute [rw] buttons
12
+ # @return [Array<Hash>]
13
+ attr_accessor :buttons
14
+
15
+ validates :buttons, presence: true, length: { maximum: Defaults::MAX_BUTTONS }
16
+
17
+ # @param buttons [Array<Hash>] The buttons of the reply buttons.
18
+ def initialize(buttons:)
19
+ @buttons = buttons
20
+
21
+ validate!
22
+ end
23
+
24
+ class << self
25
+ # @return [Hash] Serialized representation of the ReplyButtons.
26
+ def serialize(buttons:)
27
+ new(buttons:).serialize
28
+ end
29
+ end
30
+
31
+ # @return [Hash] Serialized representation of the ReplyButtons.
32
+ def serialize
33
+ {
34
+ buttons: serialized_buttons,
35
+ }
36
+ end
37
+
38
+ private
39
+
40
+ # @return [Array<Hash>] Serialized buttons.
41
+ def serialized_buttons
42
+ buttons.map { |button| Button.serialize(**button) }
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Whatsapp
4
+ class Messages
5
+ class Interactive
6
+ # Interactive call-to-action (CTA) URL button message. Maps a URL to a
7
+ # button so the raw URL does not have to appear in the message body.
8
+ # Source: https://developers.facebook.com/documentation/business-messaging/whatsapp/messages/interactive-cta-url-messages
9
+ class UrlButton < Base
10
+ module Defaults
11
+ MAX_NAME_LENGTH = 20
12
+ MAX_DISPLAY_TEXT_LENGTH = 20
13
+ end
14
+
15
+ # @!attribute [rw] name
16
+ # @return [String]
17
+ attr_accessor :name
18
+
19
+ # @!attribute [rw] display_text
20
+ # @return [String]
21
+ attr_accessor :display_text
22
+
23
+ # @!attribute [rw] url
24
+ # @return [String]
25
+ attr_accessor :url
26
+
27
+ validates :name, presence: true, length: { maximum: Defaults::MAX_NAME_LENGTH }
28
+ validates :display_text, presence: true, length: { maximum: Defaults::MAX_DISPLAY_TEXT_LENGTH }
29
+
30
+ # @param name [String] The name of the URL button.
31
+ # @param display_text [String] The text displayed on the button.
32
+ # @param url [String] The URL that the button links to.
33
+ def initialize(name:, display_text:, url:)
34
+ @name = name
35
+ @display_text = display_text
36
+ @url = url
37
+
38
+ validate!
39
+ end
40
+
41
+ class << self
42
+ # @return [Hash] Serialized representation of the URL button.
43
+ def serialize(**url_button)
44
+ new(**url_button).serialize
45
+ end
46
+ end
47
+
48
+ # @return [Hash] Serialized representation of the URL button.
49
+ def serialize
50
+ {
51
+ name:,
52
+ parameters:,
53
+ }
54
+ end
55
+
56
+ private
57
+
58
+ # @return [Hash] Serialized parameters for the URL button.
59
+ def parameters
60
+ {
61
+ display_text:,
62
+ url:,
63
+ }.compact
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Whatsapp
4
+ class Messages
5
+ class Interactive < Base
6
+ module Defaults
7
+ TYPE = "interactive"
8
+ end
9
+
10
+ # Maps a public action kind to its implementing class and the WhatsApp
11
+ # `interactive.type` value emitted in the payload. Resolution goes through
12
+ # this frozen whitelist — never `const_get` on caller input.
13
+ #
14
+ # NOTE: the carousel `api_type` values ("carousel"/"product_list") should be
15
+ # verified against the Meta docs for your API version before relying on them.
16
+ ACTION_TYPES = {
17
+ reply_buttons: { klass: ReplyButtons, api_type: "button" },
18
+ list_buttons: { klass: ListButtons, api_type: "list" },
19
+ url_button: { klass: UrlButton, api_type: "cta_url" },
20
+ media_carousel: { klass: MediaCarousel, api_type: "carousel" },
21
+ product_carousel: { klass: ProductCarousel, api_type: "product_list" },
22
+ }.freeze
23
+
24
+ # @!attribute [rw] type
25
+ # @return [Symbol] The action kind (e.g. :reply_buttons, :list_buttons).
26
+ attr_accessor :type
27
+
28
+ # @!attribute [rw] header
29
+ # @return [Hash, nil]
30
+ attr_accessor :header
31
+
32
+ # @!attribute [rw] body
33
+ # @return [String]
34
+ attr_accessor :body
35
+
36
+ # @!attribute [rw] footer
37
+ # @return [Hash, nil]
38
+ attr_accessor :footer
39
+
40
+ # @!attribute [rw] action
41
+ # @return [Hash]
42
+ attr_accessor :action
43
+
44
+ validates :type, presence: true, inclusion: { in: ACTION_TYPES.keys }
45
+ validates :body, presence: true
46
+ validates :action, presence: true
47
+
48
+ # @param type [Symbol, String] The action kind (see {ACTION_TYPES}).
49
+ # @param body [String] The body text of the interactive message.
50
+ # @param action [Hash] The action content, forwarded to the action class.
51
+ # @param header [Hash, nil] The header content (optional).
52
+ # @param footer [Hash, nil] The footer content (optional).
53
+ # @param kwargs [Hash] Additional keyword arguments (e.g. :to).
54
+ # @raise [ActiveModel::ValidationError] if validation fails.
55
+ def initialize(type:, body:, action:, header: nil, footer: nil, **)
56
+ super(**)
57
+
58
+ @type = type.to_sym
59
+ @body = body
60
+ @action = action
61
+ @header = header
62
+ @footer = footer
63
+
64
+ validate!
65
+ end
66
+
67
+ # @return [Hash] Serialized representation of the Interactive message.
68
+ def serialize
69
+ envelope(type: Defaults::TYPE, interactive: interactive_payload)
70
+ end
71
+
72
+ private
73
+
74
+ # @return [Hash] The serialized `interactive` payload.
75
+ def interactive_payload
76
+ {
77
+ type: action_config[:api_type],
78
+ header: serialized_header,
79
+ body: serialized_body,
80
+ footer: serialized_footer,
81
+ action: action_config[:klass].serialize(**action),
82
+ }.compact
83
+ end
84
+
85
+ # @return [Hash] The registry entry for the current action kind.
86
+ def action_config
87
+ ACTION_TYPES.fetch(type)
88
+ end
89
+
90
+ # @return [Hash, nil] The serialized header information.
91
+ def serialized_header
92
+ return unless header
93
+
94
+ Header.serialize(**header)
95
+ end
96
+
97
+ # @return [Hash, nil] The serialized footer information.
98
+ def serialized_footer
99
+ return unless footer
100
+
101
+ Footer.serialize(**footer)
102
+ end
103
+
104
+ # @return [Hash, nil] The serialized body information.
105
+ def serialized_body
106
+ return unless body
107
+
108
+ Body.serialize(body)
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Whatsapp
4
+ class Messages
5
+ # Location messages allow you to send a location's latitude and longitude coordinates to a WhatsApp user.
6
+ # Source: https://developers.facebook.com/documentation/business-messaging/whatsapp/messages/location-messages
7
+ class Location < Base
8
+ module Defaults
9
+ TYPE = "location"
10
+ end
11
+
12
+ # @!attribute [rw] latitude
13
+ # @return [Float]
14
+ attr_accessor :latitude
15
+
16
+ # @!attribute [rw] longitude
17
+ # @return [Float]
18
+ attr_accessor :longitude
19
+
20
+ # @!attribute [rw] name
21
+ # @return [String, nil]
22
+ attr_accessor :name
23
+
24
+ # @!attribute [rw] address
25
+ # @return [String, nil]
26
+ attr_accessor :address
27
+
28
+ validates :latitude, presence: true, numericality: true
29
+ validates :longitude, presence: true, numericality: true
30
+
31
+ # @param latitude [Float] The latitude of the location.
32
+ # @param longitude [Float] The longitude of the location.
33
+ # @param name [String, nil] The name of the location.
34
+ # @param address [String, nil] The address of the location.
35
+ # @param kwargs [Hash] Additional keyword arguments (e.g. :to).
36
+ # @raise [ActiveModel::ValidationError] if validation fails.
37
+ def initialize(latitude:, longitude:, name: nil, address: nil, **)
38
+ super(**)
39
+
40
+ @latitude = latitude
41
+ @longitude = longitude
42
+ @name = name
43
+ @address = address
44
+
45
+ validate!
46
+ end
47
+
48
+ # Serializes the location message to a hash format suitable for the WhatsApp API.
49
+ # @return [Hash] The serialized location message.
50
+ def serialize
51
+ envelope(type: Defaults::TYPE, location: location_payload)
52
+ end
53
+
54
+ private
55
+
56
+ # @return [Hash] The serialized location information.
57
+ def location_payload
58
+ {
59
+ latitude:,
60
+ longitude:,
61
+ name:,
62
+ address:,
63
+ }.compact
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Whatsapp
4
+ class Messages
5
+ class LocationRequest < Base
6
+ module Defaults
7
+ TYPE = "interactive"
8
+ INTERACTIVE_TYPE = "location_request_message"
9
+ ACTION_NAME = "send_location"
10
+ end
11
+
12
+ # @!attribute [rw] body
13
+ # @return [Hash]
14
+ attr_accessor :body
15
+
16
+ validates :body, presence: true, length: { maximum: 1024 }
17
+
18
+ # @param body [String] The body text of the location request message.
19
+ # @param kwargs [Hash] Additional keyword arguments.
20
+ # @raise [ActiveModel::ValidationError] if validation fails.
21
+ def initialize(body:, **)
22
+ super(**)
23
+
24
+ @body = body
25
+
26
+ validate!
27
+ end
28
+
29
+ def serialize
30
+ envelope(
31
+ type: Defaults::TYPE,
32
+ interactive: {
33
+ type: Defaults::INTERACTIVE_TYPE,
34
+ body:,
35
+ action: { name: Defaults::ACTION_NAME },
36
+ }
37
+ )
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Whatsapp
4
+ class Messages
5
+ # Marks a previously received message (and every earlier message in the
6
+ # conversation) as read. Unlike every other message class, this has no
7
+ # recipient or type envelope — it targets an existing message by ID.
8
+ # Meta requires this within 30 days of receipt.
9
+ # Source: https://developers.facebook.com/documentation/business-messaging/whatsapp/messages/mark-message-as-read
10
+ class MarkMessageAsRead
11
+ include ActiveModel::Validations
12
+
13
+ module Defaults
14
+ MESSAGING_PRODUCT = "whatsapp"
15
+ STATUS = "read"
16
+ end
17
+
18
+ # @!attribute [rw] message_id
19
+ # @return [String]
20
+ attr_accessor :message_id
21
+
22
+ validates :message_id, presence: true
23
+
24
+ # @param message_id [String] The WhatsApp message ID (WAMID) to mark as read.
25
+ # @raise [ActiveModel::ValidationError] if validation fails.
26
+ def initialize(message_id:)
27
+ @message_id = message_id
28
+
29
+ validate!
30
+ end
31
+
32
+ # @return [Hash] Serialized representation for the WhatsApp API.
33
+ def serialize
34
+ {
35
+ messaging_product: Defaults::MESSAGING_PRODUCT,
36
+ status: Defaults::STATUS,
37
+ message_id:,
38
+ }
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Whatsapp
4
+ class Messages
5
+ # Reaction messages are emoji-reactions applied to a WhatsApp message you have received.
6
+ # Source: https://developers.facebook.com/documentation/business-messaging/whatsapp/messages/reaction-messages
7
+ class Reaction < Base
8
+ module Defaults
9
+ TYPE = "reaction"
10
+ end
11
+
12
+ # @!attribute [rw] message_id
13
+ # @return [String]
14
+ attr_accessor :message_id
15
+
16
+ # @!attribute [rw] emoji
17
+ # @return [String]
18
+ attr_accessor :emoji
19
+
20
+ validates :message_id, presence: true
21
+ validate :emoji_unicode
22
+
23
+ # @param message_id [String] The ID of the message being reacted to.
24
+ # @param emoji [String] The emoji used for the reaction.
25
+ # @param kwargs [Hash] Additional keyword arguments.
26
+ # @raise [ActiveModel::ValidationError] if validation fails.
27
+ def initialize(message_id:, emoji:, **)
28
+ super(**)
29
+
30
+ @message_id = message_id
31
+ @emoji = emoji
32
+
33
+ validate!
34
+ end
35
+
36
+ # Serializes the reaction message to a hash format suitable for the WhatsApp API.
37
+ # @return [Hash] The serialized reaction message.
38
+ def serialize
39
+ envelope(type: Defaults::TYPE, reaction: { message_id:, emoji: })
40
+ end
41
+
42
+ private
43
+
44
+ # Validates that the emoji is a valid Unicode emoji character.
45
+ # @return [void]
46
+ # @raise [ActiveModel::ValidationError] if the emoji is not valid.
47
+ def emoji_unicode
48
+ if @emoji.nil?
49
+ errors.add(:emoji, "can't be nil")
50
+ return
51
+ end
52
+
53
+ # An empty string is valid: it removes a previously sent reaction.
54
+ return if @emoji.empty?
55
+ return if @emoji.match?(/\p{Emoji}/)
56
+
57
+ errors.add(:emoji, "must be a valid emoji character")
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Whatsapp
4
+ class Messages
5
+ class Response
6
+ class Contacts
7
+ # @!attribute [rw] input
8
+ # @return [String]
9
+ attr_accessor :input
10
+
11
+ # @!attribute [rw] wa_id
12
+ # @return [String]
13
+ attr_accessor :wa_id
14
+
15
+ # @param input [String] The input value of the contact.
16
+ # @param wa_id [String] The WhatsApp ID of the contact.
17
+ def initialize(input:, wa_id:)
18
+ @input = input
19
+ @wa_id = wa_id
20
+ end
21
+
22
+ class << self
23
+ # Deserializes a hash into a Messages::Response::Contacts object.
24
+ # @param data [Hash] The hash representation of the contact response.
25
+ # @return [Messages::Response::Contacts] The deserialized contact response object.
26
+ def deserialize(data)
27
+ new(
28
+ input: data["input"],
29
+ wa_id: data["wa_id"]
30
+ )
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Whatsapp
4
+ class Messages
5
+ class Response
6
+ class Messages
7
+ # @!attribute [rw] id
8
+ # @return [String]
9
+ attr_accessor :id
10
+
11
+ # @param id [String] The unique identifier of the message.
12
+ def initialize(id:)
13
+ @id = id
14
+ end
15
+
16
+ class << self
17
+ # Deserializes a hash into a Messages::Response::Messages object.
18
+ # @param data [Hash] The hash representation of the message response.
19
+ # @return [Messages::Response::Messages] The deserialized message response object.
20
+ def deserialize(data)
21
+ new(id: data["id"])
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Whatsapp
4
+ class Messages
5
+ class Response
6
+ # @!attribute [rw] messaging_product
7
+ # @return [String]
8
+ attr_accessor :messaging_product
9
+
10
+ # @!attribute [rw] contacts
11
+ # @return [Array<Contacts>]
12
+ attr_accessor :contacts
13
+
14
+ # @!attribute [rw] messages
15
+ # @return [Array<Messages>]
16
+ attr_accessor :messages
17
+
18
+ # @!attribute [rw] success
19
+ # @return [Boolean, nil] Present on status-update responses (e.g. mark-message-as-read);
20
+ # nil on normal message-send responses.
21
+ attr_accessor :success
22
+
23
+ # @param messaging_product [String] The messaging product used (e.g., "whatsapp").
24
+ # @param contacts [Array<Contacts>] The list of contact responses.
25
+ # @param messages [Array<Messages>] The list of message responses.
26
+ # @param success [Boolean, nil] The status-update success flag, if present.
27
+ def initialize(messaging_product:, contacts:, messages:, success: nil)
28
+ @messaging_product = messaging_product
29
+ @contacts = contacts
30
+ @messages = messages
31
+ @success = success
32
+ end
33
+
34
+ class << self
35
+ # Deserializes a hash into a Messages::Response object.
36
+ # @param data [Hash] The hash representation of the message response.
37
+ # @return [Messages::Response] The deserialized message response object.
38
+ def deserialize(data)
39
+ new(
40
+ messaging_product: data["messaging_product"],
41
+ contacts: Array(data["contacts"]).map { |contact_data| Contacts.deserialize(contact_data) },
42
+ messages: Array(data["messages"]).map { |message_data| Messages.deserialize(message_data) },
43
+ success: data["success"]
44
+ )
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end