warb 1.0.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 (85) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +8 -0
  4. data/CHANGELOG.md +25 -0
  5. data/README.md +120 -0
  6. data/Rakefile +12 -0
  7. data/docs/README.md +13 -0
  8. data/docs/components/README.md +21 -0
  9. data/docs/components/address.md +68 -0
  10. data/docs/components/cta_action.md +13 -0
  11. data/docs/components/email.md +40 -0
  12. data/docs/components/list_action.md +29 -0
  13. data/docs/components/name.md +49 -0
  14. data/docs/components/org.md +42 -0
  15. data/docs/components/phone.md +57 -0
  16. data/docs/components/reply_button_action.md +32 -0
  17. data/docs/components/row.md +13 -0
  18. data/docs/components/section.md +30 -0
  19. data/docs/components/url.md +40 -0
  20. data/docs/images/contact-with-wa_id.png +0 -0
  21. data/docs/images/contact-without-wa_id.png +0 -0
  22. data/docs/messages/README.md +79 -0
  23. data/docs/messages/audio.md +119 -0
  24. data/docs/messages/contact.md +134 -0
  25. data/docs/messages/document.md +122 -0
  26. data/docs/messages/flow.md +12 -0
  27. data/docs/messages/image.md +116 -0
  28. data/docs/messages/indicator.md +39 -0
  29. data/docs/messages/interactive_call_to_action_url.md +96 -0
  30. data/docs/messages/interactive_list.md +159 -0
  31. data/docs/messages/interactive_reply_button.md +67 -0
  32. data/docs/messages/location.md +34 -0
  33. data/docs/messages/location_request.md +21 -0
  34. data/docs/messages/reaction.md +23 -0
  35. data/docs/messages/sticker.md +116 -0
  36. data/docs/messages/text.md +47 -0
  37. data/docs/messages/video.md +116 -0
  38. data/docs/setup.md +46 -0
  39. data/docs/webhook.md +24 -0
  40. data/examples/audio.rb +86 -0
  41. data/examples/document.rb +116 -0
  42. data/examples/image.rb +97 -0
  43. data/examples/interactive_call_to_action_url.rb +177 -0
  44. data/examples/interactive_list.rb +201 -0
  45. data/examples/interactive_reply_button.rb +174 -0
  46. data/examples/location.rb +85 -0
  47. data/examples/location_request.rb +55 -0
  48. data/examples/message.rb +61 -0
  49. data/examples/sticker.rb +86 -0
  50. data/examples/video.rb +96 -0
  51. data/examples/webhook.rb +144 -0
  52. data/lib/warb/client.rb +46 -0
  53. data/lib/warb/components/action.rb +121 -0
  54. data/lib/warb/components/address.rb +31 -0
  55. data/lib/warb/components/email.rb +21 -0
  56. data/lib/warb/components/name.rb +29 -0
  57. data/lib/warb/components/org.rb +23 -0
  58. data/lib/warb/components/phone.rb +23 -0
  59. data/lib/warb/components/url.rb +21 -0
  60. data/lib/warb/configuration.rb +13 -0
  61. data/lib/warb/connection.rb +47 -0
  62. data/lib/warb/dispatcher.rb +16 -0
  63. data/lib/warb/dispatcher_concern.rb +69 -0
  64. data/lib/warb/indicator_dispatcher.rb +31 -0
  65. data/lib/warb/media_dispatcher.rb +46 -0
  66. data/lib/warb/resources/audio.rb +19 -0
  67. data/lib/warb/resources/contact.rb +89 -0
  68. data/lib/warb/resources/document.rb +32 -0
  69. data/lib/warb/resources/flow.rb +34 -0
  70. data/lib/warb/resources/image.rb +31 -0
  71. data/lib/warb/resources/interactive_call_to_action_url.rb +48 -0
  72. data/lib/warb/resources/interactive_list.rb +36 -0
  73. data/lib/warb/resources/interactive_reply_button.rb +48 -0
  74. data/lib/warb/resources/location.rb +21 -0
  75. data/lib/warb/resources/location_request.rb +24 -0
  76. data/lib/warb/resources/reaction.rb +19 -0
  77. data/lib/warb/resources/resource.rb +51 -0
  78. data/lib/warb/resources/sticker.rb +19 -0
  79. data/lib/warb/resources/text.rb +29 -0
  80. data/lib/warb/resources/video.rb +31 -0
  81. data/lib/warb/utils.rb +5 -0
  82. data/lib/warb/version.rb +5 -0
  83. data/lib/warb.rb +58 -0
  84. data/sig/warb.rbs +4 -0
  85. metadata +153 -0
@@ -0,0 +1,69 @@
1
+ module Warb
2
+ module DispatcherConcern
3
+ def message
4
+ @message ||= Dispatcher.new Resources::Text, dispatcher
5
+ end
6
+
7
+ def image
8
+ @image ||= MediaDispatcher.new Resources::Image, dispatcher
9
+ end
10
+
11
+ def video
12
+ @video ||= MediaDispatcher.new Resources::Video, dispatcher
13
+ end
14
+
15
+ def audio
16
+ @audio ||= MediaDispatcher.new Resources::Audio, dispatcher
17
+ end
18
+
19
+ def document
20
+ @document ||= MediaDispatcher.new Resources::Document, dispatcher
21
+ end
22
+
23
+ def location
24
+ @location ||= Dispatcher.new Resources::Location, dispatcher
25
+ end
26
+
27
+ def location_request
28
+ @location_request ||= Dispatcher.new Resources::LocationRequest, dispatcher
29
+ end
30
+
31
+ def interactive_reply_button
32
+ @interactive_reply_button ||= Dispatcher.new Resources::InteractiveReplyButton, dispatcher
33
+ end
34
+
35
+ def interactive_list
36
+ @interactive_list ||= Dispatcher.new Resources::InteractiveList, dispatcher
37
+ end
38
+
39
+ def interactive_call_to_action_url
40
+ @interactive_call_to_action_url ||= Dispatcher.new Resources::InteractiveCallToActionUrl, dispatcher
41
+ end
42
+
43
+ def sticker
44
+ @sticker ||= MediaDispatcher.new Resources::Sticker, dispatcher
45
+ end
46
+
47
+ def reaction
48
+ @reaction ||= Dispatcher.new Resources::Reaction, dispatcher
49
+ end
50
+
51
+ def indicator
52
+ @indicator ||= IndicatorDispatcher.new dispatcher
53
+ end
54
+
55
+ def contact
56
+ @contact ||= Dispatcher.new Resources::Contact, dispatcher
57
+ end
58
+
59
+ def flow
60
+ @flow ||= Dispatcher.new Resources::Flow, dispatcher
61
+ end
62
+
63
+ private
64
+
65
+ def dispatcher
66
+ is_a?(Client) ? self : Warb.client
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ class IndicatorDispatcher
5
+ def initialize(client)
6
+ @client = client
7
+ end
8
+
9
+ def send_typing_indicator(message_id)
10
+ data = common_indicator_params(message_id).merge(typing_indicator: { type: "text" })
11
+
12
+ @client.post("messages", data)
13
+ end
14
+
15
+ def mark_as_read(message_id)
16
+ data = common_indicator_params(message_id)
17
+
18
+ @client.post("messages", data)
19
+ end
20
+
21
+ private
22
+
23
+ def common_indicator_params(message_id)
24
+ {
25
+ messaging_product: Warb::MESSAGING_PRODUCT,
26
+ message_id: message_id,
27
+ status: "read"
28
+ }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,46 @@
1
+ module Warb
2
+ class MediaDispatcher < Dispatcher
3
+ def upload(file_path:, file_type: "text/plain")
4
+ file = Faraday::UploadIO.new(file_path, file_type)
5
+
6
+ data = { file:, messaging_product: Warb::MESSAGING_PRODUCT }
7
+
8
+ @client.post("media", data, multipart: true).body["id"]
9
+ end
10
+
11
+ def download(file_path:, media_url: nil, media_id: nil)
12
+ media_url ||= retrieve(media_id)["url"]
13
+
14
+ resp = downloaded_media_response(media_url)
15
+
16
+ File.open(file_path, "wb") do |file|
17
+ file.write(resp.body)
18
+ end
19
+ end
20
+
21
+ def retrieve(media_id)
22
+ @client.get(media_id, endpoint_prefix: nil).body
23
+ end
24
+
25
+ def delete(media_id)
26
+ response_body = @client.delete(media_id, endpoint_prefix: nil).body
27
+
28
+ return response_body["success"] if response_body["success"]
29
+
30
+ response_body["error"]["message"]
31
+ end
32
+
33
+ private
34
+
35
+ def downloaded_media_response(url)
36
+ uri = URI(url)
37
+
38
+ request = Net::HTTP::Get.new(uri)
39
+ request["Authorization"] = "Bearer #{@client.access_token}"
40
+
41
+ Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
42
+ http.request(request)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ module Resources
5
+ class Audio < Resource
6
+ attr_accessor :media_id, :link
7
+
8
+ def build_payload
9
+ {
10
+ type: "audio",
11
+ audio: {
12
+ id: media_id || @params[:media_id],
13
+ link: link || @params[:link]
14
+ }
15
+ }
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../components/address"
4
+ require_relative "../components/name"
5
+ require_relative "../components/email"
6
+ require_relative "../components/org"
7
+ require_relative "../components/phone"
8
+ require_relative "../components/url"
9
+
10
+ module Warb
11
+ module Resources
12
+ class Contact < Resource
13
+ attr_accessor :addresses, :emails, :phones, :urls, :name, :org, :birthday
14
+
15
+ def initialize(**params)
16
+ super(**params)
17
+
18
+ @org = @params[:org]
19
+ @name = @params[:name]
20
+ @birthday = @params[:birthday]
21
+ @addresses = @params.fetch(:addresses, [])
22
+ @emails = @params.fetch(:emails, [])
23
+ @phones = @params.fetch(:phones, [])
24
+ @urls = @params.fetch(:urls, [])
25
+ end
26
+
27
+ def build_payload
28
+ {
29
+ type: "contacts",
30
+ contacts: [
31
+ {
32
+ birthday: birthday,
33
+ org: org.to_h,
34
+ name: name.to_h,
35
+ urls: urls.map(&:to_h),
36
+ emails: emails.map(&:to_h),
37
+ phones: phones.map(&:to_h),
38
+ addresses: addresses.map(&:to_h)
39
+ }
40
+ ]
41
+ }
42
+ end
43
+
44
+ def add_address(**params, &block)
45
+ address = Components::Address.new(**params)
46
+
47
+ @addresses << address
48
+
49
+ block_given? ? address.tap(&block) : address
50
+ end
51
+
52
+ def add_email(**params, &block)
53
+ email = Components::Email.new(**params)
54
+
55
+ @emails << email
56
+
57
+ block_given? ? email.tap(&block) : email
58
+ end
59
+
60
+ def add_phone(**params, &block)
61
+ phone = Components::Phone.new(**params)
62
+
63
+ @phones << phone
64
+
65
+ block_given? ? phone.tap(&block) : phone
66
+ end
67
+
68
+ def add_url(**params, &block)
69
+ url = Components::URL.new(**params)
70
+
71
+ @urls << url
72
+
73
+ block_given? ? url.tap(&block) : url
74
+ end
75
+
76
+ def build_name(**params, &block)
77
+ @name = Warb::Components::Name.new(**params)
78
+
79
+ block_given? ? @name.tap(&block) : @name
80
+ end
81
+
82
+ def build_org(**params, &block)
83
+ @org = Warb::Components::Org.new(**params)
84
+
85
+ block_given? ? @org.tap(&block) : @org
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ module Resources
5
+ class Document < Resource
6
+ attr_accessor :media_id, :link, :filename, :caption
7
+
8
+ def build_header
9
+ common_document_params
10
+ end
11
+
12
+ def build_payload
13
+ params = common_document_params
14
+ params[:document][:caption] = caption || @params[:caption]
15
+ params
16
+ end
17
+
18
+ private
19
+
20
+ def common_document_params
21
+ {
22
+ type: "document",
23
+ document: {
24
+ id: media_id || @params[:media_id],
25
+ filename: filename || @params[:filename],
26
+ link: link || @params[:link]
27
+ }
28
+ }
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ module Resources
5
+ class Flow < Resource
6
+ attr_accessor :flow_id, :screen
7
+
8
+ def build_payload
9
+ {
10
+ type: "interactive",
11
+ interactive: {
12
+ type: "flow",
13
+ body: {
14
+ text: "Not shown in draft mode"
15
+ },
16
+ action: {
17
+ name: "flow",
18
+ parameters: {
19
+ flow_message_version: "3",
20
+ flow_action: "navigate",
21
+ flow_id: flow_id || @params[:flow_id],
22
+ flow_cta: "Not shown in draft mode",
23
+ mode: "draft",
24
+ flow_action_payload: {
25
+ screen: screen || @params[:screen]
26
+ }
27
+ }
28
+ }
29
+ }
30
+ }
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ module Resources
5
+ class Image < Resource
6
+ attr_accessor :media_id, :link, :caption
7
+
8
+ def build_header
9
+ common_image_params
10
+ end
11
+
12
+ def build_payload
13
+ params = common_image_params
14
+ params[:image][:caption] = caption || @params[:caption]
15
+ params
16
+ end
17
+
18
+ private
19
+
20
+ def common_image_params
21
+ {
22
+ type: "image",
23
+ image: {
24
+ id: media_id || @params[:media_id],
25
+ link: link || @params[:link]
26
+ }
27
+ }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ module Resources
5
+ class InteractiveCallToActionUrl < Resource
6
+ attr_accessor :header, :body, :footer, :action
7
+
8
+ def build_payload
9
+ {
10
+ type: "interactive",
11
+ interactive: {
12
+ type: "cta_url",
13
+ header: header || @params[:header]&.to_h,
14
+ body: {
15
+ text: body || @params[:body]
16
+ },
17
+ footer: {
18
+ text: footer || @params[:footer]
19
+ },
20
+ action: (action || @params[:action])&.to_h
21
+ }
22
+ }
23
+ end
24
+
25
+ def set_text_header(text)
26
+ @header = Warb::Resources::Text.new(text:).build_header
27
+ end
28
+
29
+ def set_image_header(link: nil)
30
+ @header = Warb::Resources::Image.new(link:).build_header
31
+ end
32
+
33
+ def set_video_header(link: nil)
34
+ @header = Warb::Resources::Video.new(link:).build_header
35
+ end
36
+
37
+ def set_document_header(link: nil, filename: nil)
38
+ @header = Warb::Resources::Document.new(link:, filename:).build_header
39
+ end
40
+
41
+ def build_action(**params, &block)
42
+ @action = Warb::Components::CTAAction.new(**params)
43
+
44
+ block_given? ? @action.tap(&block) : @action
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ module Resources
5
+ class InteractiveList < Resource
6
+ attr_accessor :header, :body, :footer, :action
7
+
8
+ def build_payload
9
+ {
10
+ type: "interactive",
11
+ interactive: {
12
+ type: "list",
13
+ header: header || @params[:header]&.to_h,
14
+ body: {
15
+ text: body || @params[:body]
16
+ },
17
+ footer: {
18
+ text: footer || @params[:footer]
19
+ },
20
+ action: (action || @params[:action])&.to_h
21
+ }
22
+ }
23
+ end
24
+
25
+ def set_text_header(text)
26
+ @header = Warb::Resources::Text.new(text:).build_header
27
+ end
28
+
29
+ def build_action(**params, &block)
30
+ @action = Warb::Components::ListAction.new(**params)
31
+
32
+ block_given? ? @action.tap(&block) : @action
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ module Resources
5
+ class InteractiveReplyButton < Resource
6
+ attr_accessor :header, :body, :footer, :action
7
+
8
+ def build_payload
9
+ {
10
+ type: "interactive",
11
+ interactive: {
12
+ type: "button",
13
+ header: header || @params[:header]&.to_h,
14
+ body: {
15
+ text: body || @params[:body]
16
+ },
17
+ footer: {
18
+ text: footer || @params[:footer]
19
+ },
20
+ action: (action || @params[:action])&.to_h
21
+ }
22
+ }
23
+ end
24
+
25
+ def set_text_header(text)
26
+ @header = Warb::Resources::Text.new(text:).build_header
27
+ end
28
+
29
+ def set_image_header(media_id: nil, link: nil)
30
+ @header = Warb::Resources::Image.new(media_id:, link:).build_header
31
+ end
32
+
33
+ def set_video_header(media_id: nil, link: nil)
34
+ @header = Warb::Resources::Video.new(media_id:, link:).build_header
35
+ end
36
+
37
+ def set_document_header(media_id: nil, link: nil, filename: nil)
38
+ @header = Warb::Resources::Document.new(media_id:, link:, filename:).build_header
39
+ end
40
+
41
+ def build_action(**params, &block)
42
+ @action = Warb::Components::ReplyButtonAction.new(**params)
43
+
44
+ block_given? ? @action.tap(&block) : @action
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ module Resources
5
+ class Location < Resource
6
+ attr_accessor :latitude, :longitude, :name, :address
7
+
8
+ def build_payload
9
+ {
10
+ type: "location",
11
+ location: {
12
+ latitude: latitude || @params[:latitude],
13
+ longitude: longitude || @params[:longitude],
14
+ name: name || @params[:name],
15
+ address: address || @params[:address]
16
+ }
17
+ }
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ module Resources
5
+ class LocationRequest < Resource
6
+ attr_accessor :body_text
7
+
8
+ def build_payload
9
+ {
10
+ type: "interactive",
11
+ interactive: {
12
+ type: "location_request_message",
13
+ body: {
14
+ text: body_text || @params[:body_text]
15
+ },
16
+ action: {
17
+ name: "send_location"
18
+ }
19
+ }
20
+ }
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ module Resources
5
+ class Reaction < Resource
6
+ attr_accessor :message_id, :emoji
7
+
8
+ def build_payload
9
+ {
10
+ type: "reaction",
11
+ reaction: {
12
+ message_id: message_id || @params[:message_id],
13
+ emoji: emoji || @params[:emoji]
14
+ }
15
+ }
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ module Resources
5
+ class Resource
6
+ def initialize(**params)
7
+ @params = params
8
+ end
9
+
10
+ def call(recipient_number, reply_to: nil)
11
+ common_resources_params(recipient_number, reply_to).merge build_payload
12
+ end
13
+
14
+ def build_header
15
+ raise NotImplementedError
16
+ end
17
+
18
+ def build_payload
19
+ raise NotImplementedError
20
+ end
21
+
22
+ def set_text_header
23
+ raise NotImplementedError
24
+ end
25
+
26
+ def set_image_header
27
+ raise NotImplementedError
28
+ end
29
+
30
+ def set_video_header
31
+ raise NotImplementedError
32
+ end
33
+
34
+ def set_document_header
35
+ raise NotImplementedError
36
+ end
37
+
38
+ protected
39
+
40
+ def common_resources_params(recipient_number, reply_to)
41
+ {
42
+ messaging_product: Warb::MESSAGING_PRODUCT,
43
+ recipient_type: Warb::RECIPIENT_TYPE,
44
+ to: recipient_number
45
+ }.tap do |params|
46
+ params[:context] = { message_id: reply_to } if reply_to
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ module Resources
5
+ class Sticker < Resource
6
+ attr_accessor :media_id, :link
7
+
8
+ def build_payload
9
+ {
10
+ type: "sticker",
11
+ sticker: {
12
+ id: media_id || @params[:media_id],
13
+ link: link || @params[:link]
14
+ }
15
+ }
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ module Resources
5
+ class Text < Resource
6
+ attr_accessor :content, :text, :message, :preview_url
7
+
8
+ def build_header
9
+ { type: "text", text: message_per_priority }
10
+ end
11
+
12
+ def build_payload
13
+ {
14
+ type: "text",
15
+ text: {
16
+ preview_url: preview_url || @params[:preview_url],
17
+ body: message_per_priority
18
+ }
19
+ }
20
+ end
21
+
22
+ private
23
+
24
+ def message_per_priority
25
+ content || text || message || @params[:content] || @params[:text] || @params[:message]
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ module Resources
5
+ class Video < Resource
6
+ attr_accessor :media_id, :link, :caption
7
+
8
+ def build_header
9
+ common_video_params
10
+ end
11
+
12
+ def build_payload
13
+ params = common_video_params
14
+ params[:video][:caption] = caption || @params[:caption]
15
+ params
16
+ end
17
+
18
+ private
19
+
20
+ def common_video_params
21
+ {
22
+ type: "video",
23
+ video: {
24
+ id: media_id || @params[:media_id],
25
+ link: link || @params[:link]
26
+ }
27
+ }
28
+ end
29
+ end
30
+ end
31
+ end
data/lib/warb/utils.rb ADDED
@@ -0,0 +1,5 @@
1
+ class String
2
+ def normalize
3
+ unicode_normalize(:nfd).gsub(/\p{Mn}/, "")
4
+ end
5
+ end