connector-ruby 0.1.1 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +61 -9
- data/README.md +157 -28
- data/connector-ruby.gemspec +5 -1
- data/lib/connector_ruby/batch_sender.rb +44 -0
- data/lib/connector_ruby/channels/line.rb +119 -0
- data/lib/connector_ruby/channels/livechat.rb +88 -0
- data/lib/connector_ruby/channels/messenger.rb +142 -0
- data/lib/connector_ruby/channels/slack.rb +115 -0
- data/lib/connector_ruby/channels/telegram.rb +18 -0
- data/lib/connector_ruby/channels/whatsapp.rb +97 -0
- data/lib/connector_ruby/configuration.rb +9 -0
- data/lib/connector_ruby/delivery_tracker.rb +58 -0
- data/lib/connector_ruby/message.rb +83 -2
- data/lib/connector_ruby/version.rb +1 -1
- data/lib/connector_ruby/webhook_verifier.rb +97 -3
- data/lib/connector_ruby.rb +10 -0
- metadata +23 -2
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ConnectorRuby
|
|
4
|
+
module Channels
|
|
5
|
+
class Messenger < Base
|
|
6
|
+
BASE_URL = "https://graph.facebook.com/v21.0/me/messages"
|
|
7
|
+
|
|
8
|
+
def initialize(page_access_token: nil)
|
|
9
|
+
@page_access_token = page_access_token || ConnectorRuby.configuration.messenger_page_access_token
|
|
10
|
+
raise ConfigurationError, "Messenger page_access_token is required" unless @page_access_token
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def send_text(to:, text:)
|
|
14
|
+
validate_send!(to: to, text: text)
|
|
15
|
+
payload = {
|
|
16
|
+
recipient: { id: to },
|
|
17
|
+
message: { text: text }
|
|
18
|
+
}
|
|
19
|
+
post_message(payload)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def send_buttons(to:, text:, buttons:)
|
|
23
|
+
validate_send!(to: to, text: text)
|
|
24
|
+
formatted = buttons.map do |btn|
|
|
25
|
+
{ type: "postback", title: btn[:title], payload: btn[:id] }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
payload = {
|
|
29
|
+
recipient: { id: to },
|
|
30
|
+
message: {
|
|
31
|
+
attachment: {
|
|
32
|
+
type: "template",
|
|
33
|
+
payload: {
|
|
34
|
+
template_type: "button",
|
|
35
|
+
text: text,
|
|
36
|
+
buttons: formatted
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
post_message(payload)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def send_image(to:, url:, caption: nil)
|
|
45
|
+
validate_send!(to: to)
|
|
46
|
+
payload = {
|
|
47
|
+
recipient: { id: to },
|
|
48
|
+
message: {
|
|
49
|
+
attachment: {
|
|
50
|
+
type: "image",
|
|
51
|
+
payload: { url: url, is_reusable: true }
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
post_message(payload)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def send_quick_replies(to:, text:, replies:)
|
|
59
|
+
validate_send!(to: to, text: text)
|
|
60
|
+
formatted = replies.map do |r|
|
|
61
|
+
{ content_type: "text", title: r[:title], payload: r[:id] }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
payload = {
|
|
65
|
+
recipient: { id: to },
|
|
66
|
+
message: { text: text, quick_replies: formatted }
|
|
67
|
+
}
|
|
68
|
+
post_message(payload)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def self.parse_webhook(body)
|
|
72
|
+
data = body.is_a?(String) ? JSON.parse(body) : body
|
|
73
|
+
|
|
74
|
+
entry = data.dig("entry", 0)
|
|
75
|
+
return nil unless entry
|
|
76
|
+
|
|
77
|
+
messaging = entry.dig("messaging", 0)
|
|
78
|
+
return nil unless messaging
|
|
79
|
+
|
|
80
|
+
if messaging["message"]
|
|
81
|
+
parse_message(messaging)
|
|
82
|
+
elsif messaging["postback"]
|
|
83
|
+
parse_postback(messaging)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def post_message(payload)
|
|
90
|
+
http_client.post(BASE_URL, body: payload, headers: auth_headers)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def auth_headers
|
|
94
|
+
{ "Authorization" => "Bearer #{@page_access_token}" }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def validate_send!(to:, text: nil)
|
|
98
|
+
raise ConnectorRuby::Error, "Recipient 'to' cannot be nil or empty" if to.nil? || to.to_s.strip.empty?
|
|
99
|
+
if text
|
|
100
|
+
raise ConnectorRuby::Error, "Text cannot be nil or empty" if text.nil? || text.to_s.strip.empty?
|
|
101
|
+
raise ConnectorRuby::Error, "Text exceeds 2000 character limit" if text.length > 2000
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def self.parse_message(messaging)
|
|
106
|
+
msg = messaging["message"]
|
|
107
|
+
sender = messaging.dig("sender", "id")
|
|
108
|
+
|
|
109
|
+
Event.new(
|
|
110
|
+
type: :message,
|
|
111
|
+
channel: :messenger,
|
|
112
|
+
from: sender,
|
|
113
|
+
to: messaging.dig("recipient", "id"),
|
|
114
|
+
text: msg["text"],
|
|
115
|
+
timestamp: messaging["timestamp"] ? Time.at(messaging["timestamp"].to_i / 1000) : nil,
|
|
116
|
+
message_id: msg["mid"],
|
|
117
|
+
metadata: {
|
|
118
|
+
is_echo: msg["is_echo"],
|
|
119
|
+
quick_reply_payload: msg.dig("quick_reply", "payload")
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def self.parse_postback(messaging)
|
|
125
|
+
postback = messaging["postback"]
|
|
126
|
+
sender = messaging.dig("sender", "id")
|
|
127
|
+
|
|
128
|
+
Event.new(
|
|
129
|
+
type: :callback,
|
|
130
|
+
channel: :messenger,
|
|
131
|
+
from: sender,
|
|
132
|
+
to: messaging.dig("recipient", "id"),
|
|
133
|
+
text: postback["payload"],
|
|
134
|
+
timestamp: messaging["timestamp"] ? Time.at(messaging["timestamp"].to_i / 1000) : nil,
|
|
135
|
+
metadata: {
|
|
136
|
+
title: postback["title"]
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ConnectorRuby
|
|
4
|
+
module Channels
|
|
5
|
+
class Slack < Base
|
|
6
|
+
BASE_URL = "https://slack.com/api"
|
|
7
|
+
|
|
8
|
+
def initialize(bot_token: nil)
|
|
9
|
+
@bot_token = bot_token || ConnectorRuby.configuration.slack_bot_token
|
|
10
|
+
raise ConfigurationError, "Slack bot_token is required" unless @bot_token
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def send_text(channel:, text:)
|
|
14
|
+
validate_send!(channel: channel, text: text)
|
|
15
|
+
payload = { channel: channel, text: text }
|
|
16
|
+
api_call("chat.postMessage", payload)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def send_buttons(channel:, text:, buttons:)
|
|
20
|
+
validate_send!(channel: channel, text: text)
|
|
21
|
+
actions = buttons.map do |btn|
|
|
22
|
+
{
|
|
23
|
+
type: "button",
|
|
24
|
+
text: { type: "plain_text", text: btn[:title] },
|
|
25
|
+
action_id: btn[:id],
|
|
26
|
+
value: btn[:id]
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
payload = {
|
|
31
|
+
channel: channel,
|
|
32
|
+
text: text,
|
|
33
|
+
blocks: [
|
|
34
|
+
{ type: "section", text: { type: "mrkdwn", text: text } },
|
|
35
|
+
{ type: "actions", elements: actions }
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
api_call("chat.postMessage", payload)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def send_image(channel:, url:, caption: nil)
|
|
42
|
+
validate_send!(channel: channel)
|
|
43
|
+
blocks = [
|
|
44
|
+
{
|
|
45
|
+
type: "image",
|
|
46
|
+
image_url: url,
|
|
47
|
+
alt_text: caption || "image"
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
blocks.first[:title] = { type: "plain_text", text: caption } if caption
|
|
51
|
+
|
|
52
|
+
payload = { channel: channel, text: caption || "Image", blocks: blocks }
|
|
53
|
+
api_call("chat.postMessage", payload)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def send_blocks(channel:, text:, blocks:)
|
|
57
|
+
validate_send!(channel: channel)
|
|
58
|
+
payload = { channel: channel, text: text, blocks: blocks }
|
|
59
|
+
api_call("chat.postMessage", payload)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def self.parse_webhook(body)
|
|
63
|
+
data = body.is_a?(String) ? JSON.parse(body) : body
|
|
64
|
+
|
|
65
|
+
# URL verification challenge
|
|
66
|
+
return { challenge: data["challenge"] } if data["type"] == "url_verification"
|
|
67
|
+
|
|
68
|
+
event = data["event"]
|
|
69
|
+
return nil unless event
|
|
70
|
+
|
|
71
|
+
case event["type"]
|
|
72
|
+
when "message"
|
|
73
|
+
return nil if event["subtype"] # skip bot messages, edits, etc.
|
|
74
|
+
parse_message(event)
|
|
75
|
+
when "app_mention"
|
|
76
|
+
parse_message(event)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def api_call(method, payload)
|
|
83
|
+
http_client.post("#{BASE_URL}/#{method}", body: payload, headers: auth_headers)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def auth_headers
|
|
87
|
+
{ "Authorization" => "Bearer #{@bot_token}" }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def validate_send!(channel:, text: nil)
|
|
91
|
+
raise ConnectorRuby::Error, "Recipient 'channel' cannot be nil or empty" if channel.nil? || channel.to_s.strip.empty?
|
|
92
|
+
if text
|
|
93
|
+
raise ConnectorRuby::Error, "Text cannot be nil or empty" if text.nil? || text.to_s.strip.empty?
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def self.parse_message(event)
|
|
98
|
+
Event.new(
|
|
99
|
+
type: :message,
|
|
100
|
+
channel: :slack,
|
|
101
|
+
from: event["user"],
|
|
102
|
+
text: event["text"],
|
|
103
|
+
timestamp: event["ts"] ? Time.at(event["ts"].to_f) : nil,
|
|
104
|
+
message_id: event["ts"],
|
|
105
|
+
metadata: {
|
|
106
|
+
channel_id: event["channel"],
|
|
107
|
+
channel_type: event["channel_type"],
|
|
108
|
+
team: event["team"],
|
|
109
|
+
thread_ts: event["thread_ts"]
|
|
110
|
+
}
|
|
111
|
+
)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -38,6 +38,24 @@ module ConnectorRuby
|
|
|
38
38
|
api_call("sendPhoto", payload)
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
+
def send_document(chat_id:, url:, caption: nil)
|
|
42
|
+
validate_send!(chat_id: chat_id)
|
|
43
|
+
payload = { chat_id: chat_id, document: url }
|
|
44
|
+
payload[:caption] = caption if caption
|
|
45
|
+
api_call("sendDocument", payload)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def send_location(chat_id:, latitude:, longitude:)
|
|
49
|
+
validate_send!(chat_id: chat_id)
|
|
50
|
+
payload = { chat_id: chat_id, latitude: latitude, longitude: longitude }
|
|
51
|
+
api_call("sendLocation", payload)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def send_typing(chat_id:)
|
|
55
|
+
validate_send!(chat_id: chat_id)
|
|
56
|
+
api_call("sendChatAction", { chat_id: chat_id, action: "typing" })
|
|
57
|
+
end
|
|
58
|
+
|
|
41
59
|
def self.parse_webhook(body)
|
|
42
60
|
data = body.is_a?(String) ? JSON.parse(body) : body
|
|
43
61
|
|
|
@@ -57,6 +57,103 @@ module ConnectorRuby
|
|
|
57
57
|
post_message(payload)
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
+
def send_template(to:, template_name:, language: "en", components: [])
|
|
61
|
+
validate_send!(to: to)
|
|
62
|
+
payload = {
|
|
63
|
+
messaging_product: "whatsapp",
|
|
64
|
+
to: to,
|
|
65
|
+
type: "template",
|
|
66
|
+
template: {
|
|
67
|
+
name: template_name,
|
|
68
|
+
language: { code: language },
|
|
69
|
+
components: components
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
post_message(payload)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def send_document(to:, url:, filename: nil, caption: nil)
|
|
76
|
+
validate_send!(to: to)
|
|
77
|
+
doc = { link: url }
|
|
78
|
+
doc[:filename] = filename if filename
|
|
79
|
+
doc[:caption] = caption if caption
|
|
80
|
+
|
|
81
|
+
payload = {
|
|
82
|
+
messaging_product: "whatsapp",
|
|
83
|
+
to: to,
|
|
84
|
+
type: "document",
|
|
85
|
+
document: doc
|
|
86
|
+
}
|
|
87
|
+
post_message(payload)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def send_location(to:, latitude:, longitude:, name: nil, address: nil)
|
|
91
|
+
validate_send!(to: to)
|
|
92
|
+
location = { latitude: latitude, longitude: longitude }
|
|
93
|
+
location[:name] = name if name
|
|
94
|
+
location[:address] = address if address
|
|
95
|
+
|
|
96
|
+
payload = {
|
|
97
|
+
messaging_product: "whatsapp",
|
|
98
|
+
to: to,
|
|
99
|
+
type: "location",
|
|
100
|
+
location: location
|
|
101
|
+
}
|
|
102
|
+
post_message(payload)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def send_contact(to:, name:, phone:)
|
|
106
|
+
validate_send!(to: to)
|
|
107
|
+
payload = {
|
|
108
|
+
messaging_product: "whatsapp",
|
|
109
|
+
to: to,
|
|
110
|
+
type: "contacts",
|
|
111
|
+
contacts: [{
|
|
112
|
+
name: { formatted_name: name },
|
|
113
|
+
phones: [{ phone: phone }]
|
|
114
|
+
}]
|
|
115
|
+
}
|
|
116
|
+
post_message(payload)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def send_reaction(to:, message_id:, emoji:)
|
|
120
|
+
validate_send!(to: to)
|
|
121
|
+
payload = {
|
|
122
|
+
messaging_product: "whatsapp",
|
|
123
|
+
to: to,
|
|
124
|
+
type: "reaction",
|
|
125
|
+
reaction: { message_id: message_id, emoji: emoji }
|
|
126
|
+
}
|
|
127
|
+
post_message(payload)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def send_list(to:, body:, button_text:, sections:)
|
|
131
|
+
validate_send!(to: to, text: body)
|
|
132
|
+
payload = {
|
|
133
|
+
messaging_product: "whatsapp",
|
|
134
|
+
to: to,
|
|
135
|
+
type: "interactive",
|
|
136
|
+
interactive: {
|
|
137
|
+
type: "list",
|
|
138
|
+
body: { text: body },
|
|
139
|
+
action: {
|
|
140
|
+
button: button_text,
|
|
141
|
+
sections: sections
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
post_message(payload)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def mark_as_read(message_id:)
|
|
149
|
+
payload = {
|
|
150
|
+
messaging_product: "whatsapp",
|
|
151
|
+
status: "read",
|
|
152
|
+
message_id: message_id
|
|
153
|
+
}
|
|
154
|
+
post_message(payload)
|
|
155
|
+
end
|
|
156
|
+
|
|
60
157
|
def self.parse_webhook(body, signature: nil)
|
|
61
158
|
data = body.is_a?(String) ? JSON.parse(body) : body
|
|
62
159
|
|
|
@@ -4,6 +4,10 @@ module ConnectorRuby
|
|
|
4
4
|
class Configuration
|
|
5
5
|
attr_accessor :whatsapp_phone_number_id, :whatsapp_access_token,
|
|
6
6
|
:telegram_bot_token,
|
|
7
|
+
:messenger_page_access_token,
|
|
8
|
+
:line_channel_access_token,
|
|
9
|
+
:slack_bot_token,
|
|
10
|
+
:livechat_pat, :livechat_region,
|
|
7
11
|
:http_timeout, :http_retries, :http_open_timeout,
|
|
8
12
|
:on_request, :on_response, :on_error
|
|
9
13
|
|
|
@@ -11,6 +15,11 @@ module ConnectorRuby
|
|
|
11
15
|
@whatsapp_phone_number_id = nil
|
|
12
16
|
@whatsapp_access_token = nil
|
|
13
17
|
@telegram_bot_token = nil
|
|
18
|
+
@messenger_page_access_token = nil
|
|
19
|
+
@line_channel_access_token = nil
|
|
20
|
+
@slack_bot_token = nil
|
|
21
|
+
@livechat_pat = nil
|
|
22
|
+
@livechat_region = nil
|
|
14
23
|
@http_timeout = 30
|
|
15
24
|
@http_retries = 3
|
|
16
25
|
@http_open_timeout = 10
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ConnectorRuby
|
|
4
|
+
class DeliveryTracker
|
|
5
|
+
attr_reader :entries
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@entries = {}
|
|
9
|
+
@callbacks = Hash.new { |h, k| h[k] = [] }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def track(message_id, metadata: {})
|
|
13
|
+
@entries[message_id] = {
|
|
14
|
+
status: :sent,
|
|
15
|
+
sent_at: Time.now,
|
|
16
|
+
metadata: metadata,
|
|
17
|
+
history: [{ status: :sent, at: Time.now }]
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def update(message_id, status:)
|
|
22
|
+
entry = @entries[message_id]
|
|
23
|
+
return nil unless entry
|
|
24
|
+
|
|
25
|
+
entry[:status] = status.to_sym
|
|
26
|
+
entry[:history] << { status: status.to_sym, at: Time.now }
|
|
27
|
+
|
|
28
|
+
fire(status.to_sym, message_id, entry)
|
|
29
|
+
entry
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def status(message_id)
|
|
33
|
+
@entries.dig(message_id, :status)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def on(status, &block)
|
|
37
|
+
@callbacks[status.to_sym] << block
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def pending
|
|
41
|
+
@entries.select { |_, v| v[:status] == :sent }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def delivered
|
|
45
|
+
@entries.select { |_, v| v[:status] == :delivered }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def read
|
|
49
|
+
@entries.select { |_, v| v[:status] == :read }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def fire(status, message_id, entry)
|
|
55
|
+
@callbacks[status].each { |cb| cb.call(message_id, entry) }
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -4,9 +4,14 @@ module ConnectorRuby
|
|
|
4
4
|
class Message
|
|
5
5
|
attr_reader :type, :to, :text, :buttons, :image_url, :caption, :metadata
|
|
6
6
|
|
|
7
|
-
TYPES = %i[text buttons image template].freeze
|
|
7
|
+
TYPES = %i[text buttons image template document location contact reaction list].freeze
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
attr_reader :document_url, :filename, :latitude, :longitude,
|
|
10
|
+
:location_name, :address, :phone, :contact_name,
|
|
11
|
+
:template_name, :language, :components,
|
|
12
|
+
:emoji, :sections, :button_text
|
|
13
|
+
|
|
14
|
+
def initialize(type:, to:, text: nil, buttons: nil, image_url: nil, caption: nil, metadata: {}, **extra)
|
|
10
15
|
@type = type
|
|
11
16
|
@to = to
|
|
12
17
|
@text = text
|
|
@@ -14,8 +19,10 @@ module ConnectorRuby
|
|
|
14
19
|
@image_url = image_url
|
|
15
20
|
@caption = caption
|
|
16
21
|
@metadata = metadata
|
|
22
|
+
extra.each { |k, v| instance_variable_set(:"@#{k}", v) }
|
|
17
23
|
end
|
|
18
24
|
|
|
25
|
+
# Factory methods
|
|
19
26
|
def self.text(to:, text:)
|
|
20
27
|
new(type: :text, to: to, text: text)
|
|
21
28
|
end
|
|
@@ -28,6 +35,24 @@ module ConnectorRuby
|
|
|
28
35
|
new(type: :image, to: to, image_url: url, caption: caption)
|
|
29
36
|
end
|
|
30
37
|
|
|
38
|
+
def self.document(to:, url:, filename: nil, caption: nil)
|
|
39
|
+
new(type: :document, to: to, document_url: url, filename: filename, caption: caption)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.location(to:, latitude:, longitude:, name: nil, address: nil)
|
|
43
|
+
new(type: :location, to: to, latitude: latitude, longitude: longitude,
|
|
44
|
+
location_name: name, address: address)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.contact(to:, name:, phone:)
|
|
48
|
+
new(type: :contact, to: to, contact_name: name, phone: phone)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Builder DSL
|
|
52
|
+
def self.build
|
|
53
|
+
Builder.new
|
|
54
|
+
end
|
|
55
|
+
|
|
31
56
|
def to_h
|
|
32
57
|
{
|
|
33
58
|
type: @type,
|
|
@@ -39,5 +64,61 @@ module ConnectorRuby
|
|
|
39
64
|
metadata: @metadata
|
|
40
65
|
}.compact
|
|
41
66
|
end
|
|
67
|
+
|
|
68
|
+
class Builder
|
|
69
|
+
def initialize
|
|
70
|
+
@attrs = { metadata: {} }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def to(recipient)
|
|
74
|
+
@attrs[:to] = recipient
|
|
75
|
+
self
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def text(content)
|
|
79
|
+
@attrs[:type] = :text
|
|
80
|
+
@attrs[:text] = content
|
|
81
|
+
self
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def buttons(btns)
|
|
85
|
+
@attrs[:type] = :buttons
|
|
86
|
+
@attrs[:buttons] = btns
|
|
87
|
+
self
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def image(url, caption: nil)
|
|
91
|
+
@attrs[:type] = :image
|
|
92
|
+
@attrs[:image_url] = url
|
|
93
|
+
@attrs[:caption] = caption
|
|
94
|
+
self
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def document(url, filename: nil)
|
|
98
|
+
@attrs[:type] = :document
|
|
99
|
+
@attrs[:document_url] = url
|
|
100
|
+
@attrs[:filename] = filename
|
|
101
|
+
self
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def location(lat, lng, name: nil)
|
|
105
|
+
@attrs[:type] = :location
|
|
106
|
+
@attrs[:latitude] = lat
|
|
107
|
+
@attrs[:longitude] = lng
|
|
108
|
+
@attrs[:location_name] = name
|
|
109
|
+
self
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def metadata(hash)
|
|
113
|
+
@attrs[:metadata] = hash
|
|
114
|
+
self
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def build
|
|
118
|
+
raise ConnectorRuby::Error, "Message requires a recipient" unless @attrs[:to]
|
|
119
|
+
raise ConnectorRuby::Error, "Message requires a type" unless @attrs[:type]
|
|
120
|
+
Message.new(**@attrs)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
42
123
|
end
|
|
43
124
|
end
|