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,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Whatsapp
4
+ class Messages
5
+ class Base
6
+ include ActiveModel::Validations
7
+
8
+ module Messaging
9
+ PRODUCT = "whatsapp"
10
+ RECIPIENT_TYPE = "individual"
11
+ end
12
+
13
+ # @!attribute [rw] to
14
+ # @return [String]
15
+ attr_accessor :to
16
+
17
+ validates :to, presence: true
18
+
19
+ # @param to [String] The recipient's phone number.
20
+ # @raise [ActiveModel::ValidationError] if validation fails.
21
+ def initialize(to:)
22
+ @to = to
23
+ end
24
+
25
+ def serialize
26
+ raise NotImplementedError, "Subclasses must implement the serialize method"
27
+ end
28
+
29
+ protected
30
+
31
+ # Builds the common message envelope shared by all message types.
32
+ # @param type [String] the WhatsApp message type (e.g. "text")
33
+ # @param body [Hash] the type-specific payload merged into the envelope
34
+ # @return [Hash]
35
+ def envelope(type:, **body)
36
+ {
37
+ messaging_product: Messaging::PRODUCT,
38
+ recipient_type: Messaging::RECIPIENT_TYPE,
39
+ to:,
40
+ type:,
41
+ **body,
42
+ }
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Whatsapp
4
+ class Messages
5
+ class Contacts
6
+ # Represents a physical address entry in a contact message.
7
+ # All fields are optional.
8
+ class Address
9
+ include ActiveModel::Validations
10
+
11
+ module Types
12
+ HOME = "HOME"
13
+ WORK = "WORK"
14
+ OTHER = "OTHER"
15
+
16
+ ALL = [HOME, WORK, OTHER].freeze
17
+ end
18
+
19
+ # @!attribute [rw] street
20
+ # @return [String, nil]
21
+ attr_accessor :street
22
+
23
+ # @!attribute [rw] city
24
+ # @return [String, nil]
25
+ attr_accessor :city
26
+
27
+ # @!attribute [rw] state
28
+ # @return [String, nil]
29
+ attr_accessor :state
30
+
31
+ # @!attribute [rw] zip
32
+ # @return [String, nil]
33
+ attr_accessor :zip
34
+
35
+ # @!attribute [rw] country
36
+ # @return [String, nil]
37
+ attr_accessor :country
38
+
39
+ # @!attribute [rw] country_code
40
+ # @return [String, nil]
41
+ attr_accessor :country_code
42
+
43
+ # @!attribute [rw] type
44
+ # @return [String, nil]
45
+ attr_accessor :type
46
+
47
+ validates :type, inclusion: { in: Types::ALL }, allow_nil: true
48
+
49
+ # @param street [String, nil]
50
+ # @param city [String, nil]
51
+ # @param state [String, nil]
52
+ # @param zip [String, nil]
53
+ # @param country [String, nil]
54
+ # @param country_code [String, nil] ISO 3166-1 alpha-2 code (e.g. "US").
55
+ # @param type [String, nil] Address type. One of HOME, WORK, OTHER.
56
+ # @raise [ActiveModel::ValidationError] if validation fails.
57
+ def initialize(street: nil, city: nil, state: nil, zip: nil, country: nil, country_code: nil, type: nil)
58
+ @street = street
59
+ @city = city
60
+ @state = state
61
+ @zip = zip
62
+ @country = country
63
+ @country_code = country_code
64
+ @type = type
65
+
66
+ validate!
67
+ end
68
+
69
+ # @return [Hash] The serialized address payload.
70
+ def serialize
71
+ {
72
+ street:,
73
+ city:,
74
+ state:,
75
+ zip:,
76
+ country:,
77
+ country_code:,
78
+ type:,
79
+ }.compact
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Whatsapp
4
+ class Messages
5
+ class Contacts
6
+ # Represents a single contact entry within a contacts message.
7
+ class Contact
8
+ include ActiveModel::Validations
9
+
10
+ # @!attribute [rw] name
11
+ # @return [Name]
12
+ attr_accessor :name
13
+
14
+ # @!attribute [rw] phones
15
+ # @return [Array<Phone>]
16
+ attr_accessor :phones
17
+
18
+ # @!attribute [rw] emails
19
+ # @return [Array<Email>]
20
+ attr_accessor :emails
21
+
22
+ # @!attribute [rw] addresses
23
+ # @return [Array<Address>]
24
+ attr_accessor :addresses
25
+
26
+ # @!attribute [rw] org
27
+ # @return [Org, nil]
28
+ attr_accessor :org
29
+
30
+ # @!attribute [rw] urls
31
+ # @return [Array<Url>]
32
+ attr_accessor :urls
33
+
34
+ # @!attribute [rw] birthday
35
+ # @return [String, nil] Date in YYYY-MM-DD format.
36
+ attr_accessor :birthday
37
+
38
+ validates :name, presence: true
39
+
40
+ # @param name [Hash, Name] The contact's name. formatted_name is required.
41
+ # @param phones [Array<Hash, Phone>] Phone numbers.
42
+ # @param emails [Array<Hash, Email>] Email addresses.
43
+ # @param addresses [Array<Hash, Address>] Physical addresses.
44
+ # @param org [Hash, Org, nil] Organization details.
45
+ # @param urls [Array<Hash, Url>] URLs.
46
+ # @param birthday [String, nil] Birthday in YYYY-MM-DD format.
47
+ # @raise [ActiveModel::ValidationError] if validation fails.
48
+ def initialize(name:, phones: [], emails: [], addresses: [], org: nil, urls: [], birthday: nil)
49
+ @name = name.is_a?(Name) ? name : Name.new(**name)
50
+ @phones = phones.map { |p| p.is_a?(Phone) ? p : Phone.new(**p) }
51
+ @emails = emails.map { |e| e.is_a?(Email) ? e : Email.new(**e) }
52
+ @addresses = addresses.map { |a| a.is_a?(Address) ? a : Address.new(**a) }
53
+ @org = if org
54
+ org.is_a?(Org) ? org : Org.new(**org)
55
+ end
56
+ @urls = urls.map { |u| u.is_a?(Url) ? u : Url.new(**u) }
57
+ @birthday = birthday
58
+
59
+ validate!
60
+ end
61
+
62
+ # @return [Hash] The serialized contact payload.
63
+ def serialize
64
+ payload = { name: name.serialize }
65
+
66
+ payload[:phones] = phones.map(&:serialize) if phones.any?
67
+ payload[:emails] = emails.map(&:serialize) if emails.any?
68
+ payload[:addresses] = addresses.map(&:serialize) if addresses.any?
69
+ payload[:org] = org.serialize if org
70
+ payload[:urls] = urls.map(&:serialize) if urls.any?
71
+ payload[:birthday] = birthday if birthday
72
+
73
+ payload
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Whatsapp
4
+ class Messages
5
+ class Contacts
6
+ # Represents an email entry in a contact message.
7
+ class Email
8
+ include ActiveModel::Validations
9
+
10
+ module Types
11
+ HOME = "HOME"
12
+ WORK = "WORK"
13
+ OTHER = "OTHER"
14
+
15
+ ALL = [HOME, WORK, OTHER].freeze
16
+ end
17
+
18
+ # @!attribute [rw] email
19
+ # @return [String]
20
+ attr_accessor :email
21
+
22
+ # @!attribute [rw] type
23
+ # @return [String, nil]
24
+ attr_accessor :type
25
+
26
+ validates :email, presence: true
27
+ validates :type, inclusion: { in: Types::ALL }, allow_nil: true
28
+
29
+ # @param email [String] The email address.
30
+ # @param type [String, nil] Email type. One of HOME, WORK, OTHER.
31
+ # @raise [ActiveModel::ValidationError] if validation fails.
32
+ def initialize(email:, type: nil)
33
+ @email = email
34
+ @type = type
35
+
36
+ validate!
37
+ end
38
+
39
+ # @return [Hash] The serialized email payload.
40
+ def serialize
41
+ {
42
+ email:,
43
+ type:,
44
+ }.compact
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 Messages
5
+ class Contacts
6
+ # Represents the name component of a contact message.
7
+ class Name
8
+ include ActiveModel::Validations
9
+
10
+ # @!attribute [rw] formatted_name
11
+ # @return [String] The contact's full display name. Required.
12
+ attr_accessor :formatted_name
13
+
14
+ # @!attribute [rw] first_name
15
+ # @return [String, nil]
16
+ attr_accessor :first_name
17
+
18
+ # @!attribute [rw] last_name
19
+ # @return [String, nil]
20
+ attr_accessor :last_name
21
+
22
+ # @!attribute [rw] middle_name
23
+ # @return [String, nil]
24
+ attr_accessor :middle_name
25
+
26
+ # @!attribute [rw] prefix
27
+ # @return [String, nil]
28
+ attr_accessor :prefix
29
+
30
+ # @!attribute [rw] suffix
31
+ # @return [String, nil]
32
+ attr_accessor :suffix
33
+
34
+ validates :formatted_name, presence: true
35
+
36
+ # @param formatted_name [String] The contact's full display name.
37
+ # @param first_name [String, nil]
38
+ # @param last_name [String, nil]
39
+ # @param middle_name [String, nil]
40
+ # @param prefix [String, nil]
41
+ # @param suffix [String, nil]
42
+ # @raise [ActiveModel::ValidationError] if validation fails.
43
+ def initialize(formatted_name:, first_name: nil, last_name: nil, middle_name: nil, prefix: nil, suffix: nil)
44
+ @formatted_name = formatted_name
45
+ @first_name = first_name
46
+ @last_name = last_name
47
+ @middle_name = middle_name
48
+ @prefix = prefix
49
+ @suffix = suffix
50
+
51
+ validate!
52
+ end
53
+
54
+ # @return [Hash] The serialized name payload.
55
+ def serialize
56
+ {
57
+ formatted_name:,
58
+ first_name:,
59
+ last_name:,
60
+ middle_name:,
61
+ prefix:,
62
+ suffix:,
63
+ }.compact
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Whatsapp
4
+ class Messages
5
+ class Contacts
6
+ # Represents the organization details of a contact message.
7
+ # All fields are optional.
8
+ class Org
9
+ # @!attribute [rw] company
10
+ # @return [String, nil]
11
+ attr_accessor :company
12
+
13
+ # @!attribute [rw] department
14
+ # @return [String, nil]
15
+ attr_accessor :department
16
+
17
+ # @!attribute [rw] title
18
+ # @return [String, nil]
19
+ attr_accessor :title
20
+
21
+ # @param company [String, nil]
22
+ # @param department [String, nil]
23
+ # @param title [String, nil]
24
+ def initialize(company: nil, department: nil, title: nil)
25
+ @company = company
26
+ @department = department
27
+ @title = title
28
+ end
29
+
30
+ # @return [Hash] The serialized org payload.
31
+ def serialize
32
+ {
33
+ company:,
34
+ department:,
35
+ title:,
36
+ }.compact
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Whatsapp
4
+ class Messages
5
+ class Contacts
6
+ # Represents a phone number entry in a contact message.
7
+ class Phone
8
+ include ActiveModel::Validations
9
+
10
+ module Types
11
+ CELL = "CELL"
12
+ MAIN = "MAIN"
13
+ IPHONE = "IPHONE"
14
+ HOME = "HOME"
15
+ WORK = "WORK"
16
+ OTHER = "OTHER"
17
+
18
+ ALL = [CELL, MAIN, IPHONE, HOME, WORK, OTHER].freeze
19
+ end
20
+
21
+ # @!attribute [rw] phone
22
+ # @return [String]
23
+ attr_accessor :phone
24
+
25
+ # @!attribute [rw] type
26
+ # @return [String, nil]
27
+ attr_accessor :type
28
+
29
+ # @!attribute [rw] wa_id
30
+ # @return [String, nil]
31
+ attr_accessor :wa_id
32
+
33
+ validates :phone, presence: true
34
+ validates :type, inclusion: { in: Types::ALL }, allow_nil: true
35
+
36
+ # @param phone [String] The phone number, including country code.
37
+ # @param type [String, nil] Phone type. One of CELL, MAIN, IPHONE, HOME, WORK, OTHER.
38
+ # @param wa_id [String, nil] The WhatsApp ID for this phone number.
39
+ # @raise [ActiveModel::ValidationError] if validation fails.
40
+ def initialize(phone:, type: nil, wa_id: nil)
41
+ @phone = phone
42
+ @type = type
43
+ @wa_id = wa_id
44
+
45
+ validate!
46
+ end
47
+
48
+ # @return [Hash] The serialized phone payload.
49
+ def serialize
50
+ {
51
+ phone:,
52
+ type:,
53
+ wa_id:,
54
+ }.compact
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Whatsapp
4
+ class Messages
5
+ class Contacts
6
+ # Represents a URL entry in a contact message.
7
+ class Url
8
+ include ActiveModel::Validations
9
+
10
+ module Types
11
+ WEBSITE = "WEBSITE"
12
+ HOMEPAGE = "HOMEPAGE"
13
+ WORK = "WORK"
14
+ HOME = "HOME"
15
+ OTHER = "OTHER"
16
+
17
+ ALL = [WEBSITE, HOMEPAGE, WORK, HOME, OTHER].freeze
18
+ end
19
+
20
+ # @!attribute [rw] url
21
+ # @return [String]
22
+ attr_accessor :url
23
+
24
+ # @!attribute [rw] type
25
+ # @return [String, nil]
26
+ attr_accessor :type
27
+
28
+ validates :url, presence: true
29
+ validates :type, inclusion: { in: Types::ALL }, allow_nil: true
30
+
31
+ # @param url [String] The URL.
32
+ # @param type [String, nil] URL type. One of WEBSITE, HOMEPAGE, WORK, HOME, OTHER.
33
+ # @raise [ActiveModel::ValidationError] if validation fails.
34
+ def initialize(url:, type: nil)
35
+ @url = url
36
+ @type = type
37
+
38
+ validate!
39
+ end
40
+
41
+ # @return [Hash] The serialized url payload.
42
+ def serialize
43
+ {
44
+ url:,
45
+ type:,
46
+ }.compact
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Whatsapp
4
+ class Messages
5
+ # Contacts messages allow you to send rich contact information directly to WhatsApp users,
6
+ # such as names, phone numbers, physical addresses, and email addresses.
7
+ # Source: https://developers.facebook.com/documentation/business-messaging/whatsapp/messages/contacts-messages
8
+ class Contacts < Base
9
+ module Defaults
10
+ TYPE = "contacts"
11
+ end
12
+
13
+ # @!attribute [rw] contacts
14
+ # @return [Array<Contact>]
15
+ attr_accessor :contacts
16
+
17
+ validates :contacts, presence: true, length: { maximum: 1 }
18
+
19
+ # @param contacts [Array<Hash, Contact>] Array with exactly one contact.
20
+ # @param kwargs [Hash] Additional keyword arguments passed to Base (:to).
21
+ # @raise [ActiveModel::ValidationError] if validation fails.
22
+ def initialize(contacts:, **)
23
+ super(**)
24
+
25
+ @contacts = contacts.map { |c| c.is_a?(Contact) ? c : Contact.new(**c) }
26
+
27
+ validate!
28
+ end
29
+
30
+ # Serializes the contacts message to a hash format suitable for the WhatsApp API.
31
+ # @return [Hash] The serialized contacts message.
32
+ def serialize
33
+ envelope(type: Defaults::TYPE, contacts: contacts.map(&:serialize))
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Whatsapp
4
+ class Messages
5
+ # Document messages allow you to send a document file with an optional caption and custom filename.
6
+ # Source: https://developers.facebook.com/documentation/business-messaging/whatsapp/messages/document-messages
7
+ #
8
+ # To send a document you must either:
9
+ # - Upload the document via Whatsapp::Media#upload to obtain a media_id, then pass it as `id`
10
+ # - Pass a publicly hosted URL as `link` (not recommended by Meta)
11
+ class Document < Base
12
+ module Defaults
13
+ TYPE = "document"
14
+ end
15
+
16
+ # @!attribute [rw] id
17
+ # @return [String, nil]
18
+ attr_accessor :id
19
+
20
+ # @!attribute [rw] link
21
+ # @return [String, nil]
22
+ attr_accessor :link
23
+
24
+ # @!attribute [rw] caption
25
+ # @return [String, nil]
26
+ attr_accessor :caption
27
+
28
+ # @!attribute [rw] filename
29
+ # @return [String, nil]
30
+ attr_accessor :filename
31
+
32
+ validate :validate_id_or_link
33
+ validates :caption, length: { maximum: 1024 }, allow_nil: true
34
+
35
+ # @param id [String, nil] ID of the uploaded media asset (from Media#upload).
36
+ # @param link [String, nil] URL of the document hosted on a public server.
37
+ # @param caption [String, nil] Document caption text (maximum 1024 characters).
38
+ # @param filename [String, nil] Custom filename displayed in the chat (e.g. "invoice.pdf").
39
+ # @param kwargs [Hash] Additional keyword arguments passed to Base (:to).
40
+ # @raise [ActiveModel::ValidationError] if validation fails.
41
+ def initialize(id: nil, link: nil, caption: nil, filename: nil, **)
42
+ super(**)
43
+
44
+ @id = id
45
+ @link = link
46
+ @caption = caption
47
+ @filename = filename
48
+
49
+ validate!
50
+ end
51
+
52
+ # Serializes the document message to a hash format suitable for the WhatsApp API.
53
+ # @return [Hash] The serialized document message.
54
+ def serialize
55
+ envelope(type: Defaults::TYPE, document: document_payload)
56
+ end
57
+
58
+ private
59
+
60
+ # @return [Hash] The serialized document payload.
61
+ def document_payload
62
+ {
63
+ id:,
64
+ link:,
65
+ caption:,
66
+ filename:,
67
+ }.compact
68
+ end
69
+
70
+ # Validates that either id or link is present.
71
+ # @return [void]
72
+ def validate_id_or_link
73
+ return if id.present? || link.present?
74
+
75
+ errors.add(:base, "Either id or link must be present")
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Whatsapp
4
+ class Messages
5
+ # Image messages display an image with an optional caption.
6
+ # Source: https://developers.facebook.com/documentation/business-messaging/whatsapp/messages/image-messages
7
+ #
8
+ # To send an image you must either:
9
+ # - Upload the image via Whatsapp::Media#upload to obtain a media_id, then pass it as `id`
10
+ # - Pass a publicly hosted URL as `link` (not recommended by Meta)
11
+ class Image < Base
12
+ module Defaults
13
+ TYPE = "image"
14
+ end
15
+
16
+ # @!attribute [rw] id
17
+ # @return [String, nil]
18
+ attr_accessor :id
19
+
20
+ # @!attribute [rw] link
21
+ # @return [String, nil]
22
+ attr_accessor :link
23
+
24
+ # @!attribute [rw] caption
25
+ # @return [String, nil]
26
+ attr_accessor :caption
27
+
28
+ validate :validate_id_or_link
29
+ validates :caption, length: { maximum: 1024 }, allow_nil: true
30
+
31
+ # @param id [String, nil] ID of the uploaded media asset (from Media#upload).
32
+ # @param link [String, nil] URL of the image hosted on a public server.
33
+ # @param caption [String, nil] Image caption text (maximum 1024 characters).
34
+ # @param kwargs [Hash] Additional keyword arguments passed to Base (:to).
35
+ # @raise [ActiveModel::ValidationError] if validation fails.
36
+ def initialize(id: nil, link: nil, caption: nil, **)
37
+ super(**)
38
+
39
+ @id = id
40
+ @link = link
41
+ @caption = caption
42
+
43
+ validate!
44
+ end
45
+
46
+ # Serializes the image message to a hash format suitable for the WhatsApp API.
47
+ # @return [Hash] The serialized image message.
48
+ def serialize
49
+ envelope(type: Defaults::TYPE, image: image_payload)
50
+ end
51
+
52
+ private
53
+
54
+ # @return [Hash] The serialized image payload.
55
+ def image_payload
56
+ {
57
+ id:,
58
+ link:,
59
+ caption:,
60
+ }.compact
61
+ end
62
+
63
+ # Validates that either id or link is present.
64
+ # @return [void]
65
+ def validate_id_or_link
66
+ return if id.present? || link.present?
67
+
68
+ errors.add(:base, "Either id or link must be present")
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Whatsapp
4
+ class Messages
5
+ class Interactive
6
+ class Base
7
+ include ActiveModel::Validations
8
+
9
+ def serialize
10
+ raise NotImplementedError, "Subclasses must implement the serialize method"
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end