warb 0.1.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 (104) 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 +135 -0
  6. data/Rakefile +12 -0
  7. data/docs/README.md +16 -0
  8. data/docs/components/README.md +24 -0
  9. data/docs/components/address.md +68 -0
  10. data/docs/components/button.md +61 -0
  11. data/docs/components/copy_code_button.md +57 -0
  12. data/docs/components/cta_action.md +13 -0
  13. data/docs/components/email.md +40 -0
  14. data/docs/components/list_action.md +29 -0
  15. data/docs/components/name.md +49 -0
  16. data/docs/components/org.md +42 -0
  17. data/docs/components/phone.md +57 -0
  18. data/docs/components/reply_button_action.md +32 -0
  19. data/docs/components/row.md +13 -0
  20. data/docs/components/section.md +30 -0
  21. data/docs/components/url.md +40 -0
  22. data/docs/components/url_button.md +57 -0
  23. data/docs/images/contact-with-wa_id.png +0 -0
  24. data/docs/images/contact-without-wa_id.png +0 -0
  25. data/docs/messages/README.md +80 -0
  26. data/docs/messages/audio.md +119 -0
  27. data/docs/messages/contact.md +134 -0
  28. data/docs/messages/document.md +122 -0
  29. data/docs/messages/flow.md +12 -0
  30. data/docs/messages/image.md +116 -0
  31. data/docs/messages/indicator.md +39 -0
  32. data/docs/messages/interactive_call_to_action_url.md +96 -0
  33. data/docs/messages/interactive_list.md +159 -0
  34. data/docs/messages/interactive_reply_button.md +67 -0
  35. data/docs/messages/location.md +34 -0
  36. data/docs/messages/location_request.md +21 -0
  37. data/docs/messages/reaction.md +23 -0
  38. data/docs/messages/sticker.md +116 -0
  39. data/docs/messages/template.md +327 -0
  40. data/docs/messages/text.md +47 -0
  41. data/docs/messages/video.md +116 -0
  42. data/docs/resources/currency.md +22 -0
  43. data/docs/resources/date_time.md +11 -0
  44. data/docs/resources/index.md +14 -0
  45. data/docs/resources/text.md +9 -0
  46. data/docs/setup.md +46 -0
  47. data/docs/webhook.md +24 -0
  48. data/examples/audio.rb +86 -0
  49. data/examples/document.rb +116 -0
  50. data/examples/image.rb +97 -0
  51. data/examples/interactive_call_to_action_url.rb +177 -0
  52. data/examples/interactive_list.rb +201 -0
  53. data/examples/interactive_reply_button.rb +174 -0
  54. data/examples/location.rb +85 -0
  55. data/examples/location_request.rb +55 -0
  56. data/examples/message.rb +61 -0
  57. data/examples/sticker.rb +86 -0
  58. data/examples/video.rb +96 -0
  59. data/examples/webhook.rb +144 -0
  60. data/lib/warb/client.rb +46 -0
  61. data/lib/warb/components/action.rb +121 -0
  62. data/lib/warb/components/address.rb +31 -0
  63. data/lib/warb/components/button.rb +29 -0
  64. data/lib/warb/components/component.rb +19 -0
  65. data/lib/warb/components/copy_code_button.rb +30 -0
  66. data/lib/warb/components/email.rb +21 -0
  67. data/lib/warb/components/name.rb +29 -0
  68. data/lib/warb/components/org.rb +23 -0
  69. data/lib/warb/components/phone.rb +23 -0
  70. data/lib/warb/components/quick_reply_button.rb +15 -0
  71. data/lib/warb/components/url.rb +21 -0
  72. data/lib/warb/components/url_button.rb +30 -0
  73. data/lib/warb/components/voice_call_button.rb +15 -0
  74. data/lib/warb/configuration.rb +13 -0
  75. data/lib/warb/connection.rb +47 -0
  76. data/lib/warb/dispatcher.rb +16 -0
  77. data/lib/warb/dispatcher_concern.rb +73 -0
  78. data/lib/warb/indicator_dispatcher.rb +31 -0
  79. data/lib/warb/language.rb +8 -0
  80. data/lib/warb/media_dispatcher.rb +46 -0
  81. data/lib/warb/resources/audio.rb +19 -0
  82. data/lib/warb/resources/contact.rb +89 -0
  83. data/lib/warb/resources/currency.rb +45 -0
  84. data/lib/warb/resources/date_time.rb +34 -0
  85. data/lib/warb/resources/document.rb +32 -0
  86. data/lib/warb/resources/flow.rb +34 -0
  87. data/lib/warb/resources/image.rb +31 -0
  88. data/lib/warb/resources/interactive_call_to_action_url.rb +48 -0
  89. data/lib/warb/resources/interactive_list.rb +36 -0
  90. data/lib/warb/resources/interactive_reply_button.rb +48 -0
  91. data/lib/warb/resources/location.rb +31 -0
  92. data/lib/warb/resources/location_request.rb +24 -0
  93. data/lib/warb/resources/reaction.rb +19 -0
  94. data/lib/warb/resources/resource.rb +59 -0
  95. data/lib/warb/resources/sticker.rb +19 -0
  96. data/lib/warb/resources/template.rb +166 -0
  97. data/lib/warb/resources/text.rb +47 -0
  98. data/lib/warb/resources/video.rb +31 -0
  99. data/lib/warb/template_dispatcher.rb +10 -0
  100. data/lib/warb/utils.rb +5 -0
  101. data/lib/warb/version.rb +5 -0
  102. data/lib/warb.rb +69 -0
  103. data/sig/warb.rbs +4 -0
  104. metadata +172 -0
@@ -0,0 +1,144 @@
1
+ require "sinatra/base"
2
+ require "faraday"
3
+
4
+ class Webhook < Sinatra::Base
5
+ configure do
6
+ set :bind, "0.0.0.0"
7
+ set :port, 3000
8
+ set :host_authorization, { permitted_hosts: [] }
9
+ end
10
+
11
+ post "/webhook" do
12
+ request_body = JSON.parse(request.body.read)
13
+
14
+ puts "\n🪝 Incoming webhook message: #{request_body}"
15
+
16
+ message = request_body.dig("entry", 0, "changes", 0, "value", "messages", 0)
17
+
18
+ if message && message["type"] == "text"
19
+ message_id = message["id"]
20
+
21
+ Warb.indicator.mark_as_read(message_id)
22
+
23
+ Warb.message.dispatch(message["from"], reply_to: message_id, message: "Echo #{message["text"]["body"]}")
24
+
25
+ reaction = {
26
+ message_id:,
27
+ emoji: "✅"
28
+ }
29
+
30
+ Warb.reaction.dispatch(message["from"], **reaction)
31
+ elsif message && message["type"] == "location"
32
+ message_id = message["id"]
33
+
34
+ Warb.indicator.mark_as_read(message_id)
35
+
36
+ # indicate the response is being processed
37
+ Warb.indicator.send_typing_indicator(message_id)
38
+
39
+ # wait a little before send the actual response, so the user can see the typing indicator
40
+ sleep 2
41
+
42
+ location = {
43
+ latitude: message["location"]["latitude"],
44
+ longitude: message["location"]["longitude"],
45
+ name: message["location"]["name"],
46
+ address: message["location"]["address"]
47
+ }
48
+
49
+ Warb.location.dispatch(message["from"], reply_to: message_id, **location)
50
+ elsif message && message["type"] == "image"
51
+ message_id = message["id"]
52
+
53
+ Warb.indicator.mark_as_read(message_id)
54
+
55
+ # for images, documents, videos, audios and stickers, you may need to keep the respective field id
56
+ # the respective ids are:
57
+ # message["image"]["id"]
58
+ # message["sticker"]["id"]
59
+ # message["audio"]["id"]
60
+ # message["document"]["id"]
61
+ # message["video"]["id"]
62
+ #
63
+ # and only one of them is presented within the webhook response data
64
+ #
65
+ # with the media id in hands, you can re-send it and/or download it
66
+ #
67
+ # below, we resend the received image, using its id.
68
+
69
+ image = {
70
+ media_id: message["image"]["id"],
71
+ link: message["image"]["link"],
72
+ caption: message["image"]["caption"]
73
+ }
74
+
75
+ Warb.image.dispatch(message["from"], reply_to: message_id, **image)
76
+
77
+ # and here, we download the received image
78
+ Warb.image.download(media_id: image[:media_id], file_path: "#{Time.now}.jpg")
79
+ elsif message && message["type"] == "document"
80
+ message_id = message["id"]
81
+
82
+ Warb.indicator.mark_as_read(message_id)
83
+
84
+ document = {
85
+ id: message["document"]["id"],
86
+ link: message["document"]["link"],
87
+ caption: message["document"]["caption"],
88
+ filename: message["document"]["filename"]
89
+ }
90
+
91
+ Warb.document.dispatch(message["from"], reply_to: message_id, **document)
92
+ elsif message && message["type"] == "sticker"
93
+ message_id = message["id"]
94
+
95
+ Warb.indicator.mark_as_read(message_id)
96
+
97
+ sticker = {
98
+ media_id: message["sticker"]["id"],
99
+ link: message["sticker"]["link"]
100
+ }
101
+
102
+ Warb.sticker.dispatch(message["from"], reply_to: message_id, **sticker)
103
+ # you could keep adding verifications for different types of messages...
104
+ # elsif message && message["type"] == "image"
105
+ # elsif message && message["type"] == "video"
106
+ # elsif message && message["type"] == "contacts ?"
107
+ # elsif message && message["type"] == "button"
108
+ # elsif message && message["type"] == "interactive"
109
+ # elsif message && message["type"] == "unknown"
110
+ end
111
+
112
+ status 200
113
+ end
114
+
115
+ # this is the endpoint which gets called to verify the server within the Meta's API
116
+ # you can do whatever you want here to verify your server
117
+ # returning the challenge value which was received as query param is enough
118
+ get "/webhook" do
119
+ mode = params["hub.mode"]
120
+ token = params["hub.verify_token"]
121
+ challenge = params["hub.challenge"]
122
+
123
+ if mode == "subscribe" && token == Warb.configuration.webhook_verify_token
124
+ status 200
125
+ body challenge
126
+
127
+ puts "\n✨ Webhook verified successfully!"
128
+ else
129
+ status 403
130
+ end
131
+ end
132
+
133
+ run! if app_file == $PROGRAM_NAME
134
+
135
+ private
136
+
137
+ def conn
138
+ @conn ||= Faraday.new("https://graph.facebook.com/v22.0") do |conn|
139
+ conn.headers["Authorization"] = "Bearer #{Warb.configuration.access_token}" if Warb.configuration.access_token
140
+ conn.request(:json)
141
+ conn.response(:json)
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "connection"
4
+ module Warb
5
+ class Client
6
+ include DispatcherConcern
7
+ extend Forwardable
8
+
9
+ attr_reader :access_token, :sender_id, :business_id, :adapter, :logger
10
+
11
+ def_delegators :@configuration, :access_token, :sender_id, :business_id, :adapter, :logger
12
+
13
+ def initialize(configuration = nil, access_token: nil, sender_id: nil, business_id: nil,
14
+ adapter: nil, logger: nil)
15
+ @configuration = (configuration || Warb.configuration).dup
16
+
17
+ @configuration.access_token = access_token || @configuration.access_token
18
+ @configuration.sender_id = sender_id || @configuration.sender_id
19
+ @configuration.business_id = business_id || @configuration.business_id
20
+ @configuration.adapter = adapter || @configuration.adapter
21
+ @configuration.logger = logger || @configuration.logger
22
+ end
23
+
24
+ def get(endpoint, data = {}, **args)
25
+ conn.send_request(http_method: "get", endpoint: endpoint, data: data, **args)
26
+ end
27
+
28
+ def post(endpoint, data = {}, **args)
29
+ conn.send_request(http_method: "post", endpoint: endpoint, data: data, **args)
30
+ end
31
+
32
+ def put(endpoint, data = {}, **args)
33
+ conn.send_request(http_method: "put", endpoint: endpoint, data: data, **args)
34
+ end
35
+
36
+ def delete(endpoint, data = {}, **args)
37
+ conn.send_request(http_method: "delete", endpoint: endpoint, data: data, **args)
38
+ end
39
+
40
+ private
41
+
42
+ def conn
43
+ @conn ||= Warb::Connection.new(self)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,121 @@
1
+ module Warb
2
+ module Components
3
+ class Row
4
+ attr_accessor :title, :description
5
+
6
+ def initialize(title: nil, description: nil)
7
+ @title = title
8
+ @description = description
9
+ end
10
+
11
+ def to_h
12
+ {
13
+ title: @title,
14
+ description: @description
15
+ }
16
+ end
17
+ end
18
+
19
+ class Section
20
+ attr_accessor :title, :rows
21
+
22
+ def initialize(title: nil, rows: [])
23
+ @title = title
24
+ @rows = rows
25
+ end
26
+
27
+ def add_row(**args, &block)
28
+ row = Row.new(**args)
29
+
30
+ @rows << row
31
+
32
+ block_given? ? row.tap(&block) : row
33
+ end
34
+
35
+ def to_h
36
+ {
37
+ title: @title,
38
+ rows: @rows.map.with_index do |row, index|
39
+ row_title = row.title.slice(0, 10)
40
+ title = row_title.normalize.gsub(/\s/, "").downcase
41
+ id = "#{title}_#{index}"
42
+
43
+ row.to_h.merge(id: id)
44
+ end
45
+ }
46
+ end
47
+ end
48
+
49
+ class ListAction
50
+ attr_accessor :button_text, :sections
51
+
52
+ def initialize(button_text: nil, sections: [])
53
+ @button_text = button_text
54
+ @sections = sections
55
+ end
56
+
57
+ def add_section(**args, &block)
58
+ section = Section.new(**args)
59
+
60
+ @sections << section
61
+
62
+ block_given? ? section.tap(&block) : section
63
+ end
64
+
65
+ def to_h
66
+ {
67
+ button: @button_text,
68
+ sections: @sections.map(&:to_h)
69
+ }
70
+ end
71
+ end
72
+
73
+ class ReplyButtonAction
74
+ attr_accessor :buttons_texts
75
+
76
+ def initialize(buttons_texts: [])
77
+ @buttons_texts = buttons_texts
78
+ end
79
+
80
+ def to_h
81
+ {
82
+ buttons: @buttons_texts.map.with_index do |button_text, index|
83
+ text = button_text.normalize.gsub(/\s/, "").downcase
84
+ id = "#{text}_#{index}"
85
+
86
+ {
87
+ type: "reply",
88
+ reply: {
89
+ id: id,
90
+ title: button_text
91
+ }
92
+ }
93
+ end
94
+ }
95
+ end
96
+
97
+ def add_button_text(button_text)
98
+ @buttons_texts << button_text
99
+ end
100
+ end
101
+
102
+ class CTAAction
103
+ attr_accessor :button_text, :url
104
+
105
+ def initialize(button_text: nil, url: nil)
106
+ @button_text = button_text
107
+ @url = url
108
+ end
109
+
110
+ def to_h
111
+ {
112
+ name: "cta_url",
113
+ parameters: {
114
+ display_text: @button_text,
115
+ url: @url
116
+ }
117
+ }
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ module Components
5
+ class Address
6
+ attr_accessor :street, :city, :state, :zip, :country, :country_code, :type
7
+
8
+ def initialize(**params)
9
+ @street = params[:street]
10
+ @city = params[:city]
11
+ @state = params[:state]
12
+ @zip = params[:zip]
13
+ @country = params[:country]
14
+ @country_code = params[:country_code]
15
+ @type = params[:type]
16
+ end
17
+
18
+ def to_h
19
+ {
20
+ street: street,
21
+ city: city,
22
+ state: state,
23
+ zip: zip,
24
+ country: country,
25
+ country_code: country_code,
26
+ type: type
27
+ }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ module Components
5
+ class Button < Component
6
+ attr_accessor :index, :sub_type
7
+
8
+ def initialize(**params)
9
+ params[:sub_type] = button_type unless params[:sub_type]
10
+
11
+ super(**params)
12
+ end
13
+
14
+ def to_h
15
+ {
16
+ type: "button",
17
+ sub_type: sub_type || @params[:sub_type],
18
+ index: index || @params[:index]
19
+ }
20
+ end
21
+
22
+ private
23
+
24
+ def button_type
25
+ raise NotImplementedError
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ module Components
5
+ class Component
6
+ def initialize(**params)
7
+ @params = params
8
+ end
9
+
10
+ def to_h
11
+ raise NotImplementedError
12
+ end
13
+
14
+ protected
15
+
16
+ attr_reader :params
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ module Components
5
+ class CopyCodeButton < Button
6
+ BUTTON_TYPE = "copy_code"
7
+
8
+ attr_accessor :coupon_code
9
+
10
+ def to_h
11
+ button_payload = super
12
+
13
+ if coupon_code || @params[:coupon_code]
14
+ button_payload[:parameters] = Array.new(1, {
15
+ type: "coupon_code",
16
+ coupon_code: coupon_code || @params[:coupon_code]
17
+ })
18
+ end
19
+
20
+ button_payload
21
+ end
22
+
23
+ private
24
+
25
+ def button_type
26
+ BUTTON_TYPE
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ module Components
5
+ class Email
6
+ attr_accessor :type, :email
7
+
8
+ def initialize(**params)
9
+ @type = params[:type]
10
+ @email = params[:email]
11
+ end
12
+
13
+ def to_h
14
+ {
15
+ type: type,
16
+ email: email
17
+ }
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ module Components
5
+ class Name
6
+ attr_accessor :formatted_name, :first_name, :last_name, :middle_name, :suffix, :prefix
7
+
8
+ def initialize(**params)
9
+ @formatted_name = params[:formatted_name]
10
+ @first_name = params[:first_name]
11
+ @last_name = params[:last_name]
12
+ @middle_name = params[:middle_name]
13
+ @suffix = params[:suffix]
14
+ @prefix = params[:prefix]
15
+ end
16
+
17
+ def to_h
18
+ {
19
+ formatted_name: formatted_name,
20
+ first_name: first_name,
21
+ last_name: last_name,
22
+ middle_name: middle_name,
23
+ suffix: suffix,
24
+ prefix: prefix
25
+ }
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ module Components
5
+ class Org
6
+ attr_accessor :company, :department, :title
7
+
8
+ def initialize(**params)
9
+ @company = params[:company]
10
+ @department = params[:department]
11
+ @title = params[:title]
12
+ end
13
+
14
+ def to_h
15
+ {
16
+ company: company,
17
+ department: department,
18
+ title: title
19
+ }
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ module Components
5
+ class Phone
6
+ attr_accessor :phone, :type, :wa_id
7
+
8
+ def initialize(**params)
9
+ @phone = params[:phone]
10
+ @type = params[:type]
11
+ @wa_id = params[:wa_id]
12
+ end
13
+
14
+ def to_h
15
+ {
16
+ phone: phone,
17
+ type: type,
18
+ wa_id: wa_id
19
+ }
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ module Components
5
+ class QuickReplyButton < Button
6
+ BUTTON_TYPE = "quick_reply"
7
+
8
+ private
9
+
10
+ def button_type
11
+ BUTTON_TYPE
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ module Components
5
+ class URL
6
+ attr_accessor :url, :type
7
+
8
+ def initialize(**params)
9
+ @type = params[:type]
10
+ @url = params[:url]
11
+ end
12
+
13
+ def to_h
14
+ {
15
+ type: type,
16
+ url: url
17
+ }
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ module Components
5
+ class UrlButton < Button
6
+ BUTTON_TYPE = "url"
7
+
8
+ attr_accessor :text
9
+
10
+ def to_h
11
+ button_payload = super
12
+
13
+ if text || @params[:text]
14
+ button_payload[:parameters] = Array.new(1, {
15
+ type: "text",
16
+ text: text || @params[:text]
17
+ })
18
+ end
19
+
20
+ button_payload
21
+ end
22
+
23
+ private
24
+
25
+ def button_type
26
+ BUTTON_TYPE
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ module Components
5
+ class VoiceCallButton < Button
6
+ BUTTON_TYPE = "voice_call"
7
+
8
+ private
9
+
10
+ def button_type
11
+ BUTTON_TYPE
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ module Warb
2
+ class Configuration
3
+ attr_accessor :access_token, :sender_id, :business_id, :adapter, :logger
4
+
5
+ def initialize(access_token: nil, sender_id: nil, business_id: nil, adapter: nil, logger: nil)
6
+ @access_token = access_token
7
+ @sender_id = sender_id
8
+ @business_id = business_id
9
+ @adapter = adapter || Faraday.default_adapter
10
+ @logger = logger || Logger.new($stdout)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Warb
4
+ class Connection
5
+ attr_reader :client
6
+
7
+ def initialize(client)
8
+ @client = client
9
+ end
10
+
11
+ def send_request(http_method:, endpoint:, url: nil, data: {}, headers: {}, multipart: false,
12
+ endpoint_prefix: :sender_id)
13
+ conn = set_connection(url:, multipart:)
14
+ conn.send(http_method, handle_endpoint(endpoint:, endpoint_prefix:), data, headers)
15
+ rescue StandardError => e
16
+ @client.logger.error e.inspect
17
+ e.response
18
+ end
19
+
20
+ private
21
+
22
+ def set_connection(url:, multipart:)
23
+ url ||= "https://graph.facebook.com/v22.0"
24
+
25
+ Faraday.new(url) do |conn|
26
+ conn.request(:multipart) if multipart
27
+ conn.request(:url_encoded) if multipart
28
+ conn.request(:json)
29
+ conn.response(:json)
30
+ conn.headers["Authorization"] = "Bearer #{@client.access_token}" unless @client.access_token.nil?
31
+ conn.adapter(@client.adapter)
32
+ conn.response :raise_error
33
+ end
34
+ end
35
+
36
+ def handle_endpoint(endpoint:, endpoint_prefix:)
37
+ case endpoint_prefix
38
+ when :sender_id
39
+ "#{@client.sender_id}/#{endpoint}"
40
+ when :business_id
41
+ "#{@client.business_id}/#{endpoint}"
42
+ else
43
+ endpoint
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,16 @@
1
+ module Warb
2
+ class Dispatcher
3
+ def initialize(klass, client)
4
+ @klass = klass
5
+ @client = client
6
+ end
7
+
8
+ def dispatch(recipient_number, reply_to: nil, **args, &block)
9
+ resource = block_given? ? @klass.new(**args).tap(&block) : @klass.new(**args)
10
+
11
+ data = resource.call(recipient_number, reply_to:)
12
+
13
+ @client.post("messages", data)
14
+ end
15
+ end
16
+ end