stealth 0.9.5 → 0.9.6
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/Gemfile.lock +13 -16
- data/VERSION +1 -1
- data/lib/stealth/base.rb +1 -1
- data/lib/stealth/controller.rb +7 -7
- data/lib/stealth/dispatcher.rb +1 -1
- data/lib/stealth/flow/base.rb +1 -1
- data/lib/stealth/server.rb +1 -1
- data/lib/stealth/services/base_client.rb +1 -4
- data/lib/stealth/services/base_message_handler.rb +1 -1
- data/lib/stealth/session.rb +1 -1
- data/stealth.gemspec +0 -1
- metadata +3 -23
- data/lib/stealth/services/facebook/client.rb +0 -60
- data/lib/stealth/services/facebook/events/message_event.rb +0 -59
- data/lib/stealth/services/facebook/events/postback_event.rb +0 -36
- data/lib/stealth/services/facebook/message_handler.rb +0 -84
- data/lib/stealth/services/facebook/reply_handler.rb +0 -480
- data/lib/stealth/services/facebook/setup.rb +0 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5b2f50621f9aca430ba407f83791be90ddec4400
|
4
|
+
data.tar.gz: 5a2bbecfc460f3350437266dc65a9b954ea57229
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 59aa56ef208c1a00ee25102c330b67593c087284fd96fd146327208c0152ad0015724173daddedb9e6c460eb5ccd9b0e3ba52db42d01e26798f7664f57d053e4
|
7
|
+
data.tar.gz: 1f7886d7a3acb82bd11619536744c5a5045c1718cc7f70bbc32ee7067b1bc1c14a43561e18d359234bc3f1dfe698633e6df3de25250d53ac52b35d52142b7022
|
data/Gemfile.lock
CHANGED
@@ -1,19 +1,18 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
stealth (0.9.
|
4
|
+
stealth (0.9.5)
|
5
5
|
activesupport (~> 5.1)
|
6
|
-
faraday (~> 0.13)
|
7
6
|
multi_json (~> 1.12)
|
8
|
-
puma (~> 3.10
|
7
|
+
puma (~> 3.10)
|
9
8
|
sidekiq (~> 5.0)
|
10
|
-
sinatra (~> 2.0
|
9
|
+
sinatra (~> 2.0)
|
11
10
|
thor (~> 0.20)
|
12
11
|
|
13
12
|
GEM
|
14
13
|
remote: https://rubygems.org/
|
15
14
|
specs:
|
16
|
-
activesupport (5.1.
|
15
|
+
activesupport (5.1.4)
|
17
16
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
18
17
|
i18n (~> 0.7)
|
19
18
|
minitest (~> 5.1)
|
@@ -21,12 +20,10 @@ GEM
|
|
21
20
|
concurrent-ruby (1.0.5)
|
22
21
|
connection_pool (2.2.1)
|
23
22
|
diff-lcs (1.3)
|
24
|
-
|
25
|
-
|
26
|
-
i18n (0.8.6)
|
23
|
+
i18n (0.9.1)
|
24
|
+
concurrent-ruby (~> 1.0)
|
27
25
|
minitest (5.10.3)
|
28
26
|
multi_json (1.12.2)
|
29
|
-
multipart-post (2.0.0)
|
30
27
|
mustermann (1.0.1)
|
31
28
|
oj (3.3.6)
|
32
29
|
puma (3.10.0)
|
@@ -35,7 +32,7 @@ GEM
|
|
35
32
|
rack
|
36
33
|
rack-test (0.7.0)
|
37
34
|
rack (>= 1.0, < 3)
|
38
|
-
redis (
|
35
|
+
redis (4.0.1)
|
39
36
|
rspec (3.6.0)
|
40
37
|
rspec-core (~> 3.6.0)
|
41
38
|
rspec-expectations (~> 3.6.0)
|
@@ -51,11 +48,11 @@ GEM
|
|
51
48
|
rspec-support (3.6.0)
|
52
49
|
rspec_junit_formatter (0.3.0)
|
53
50
|
rspec-core (>= 2, < 4, != 2.12.0)
|
54
|
-
sidekiq (5.0.
|
51
|
+
sidekiq (5.0.5)
|
55
52
|
concurrent-ruby (~> 1.0)
|
56
53
|
connection_pool (~> 2.2, >= 2.2.0)
|
57
54
|
rack-protection (>= 1.5.0)
|
58
|
-
redis (
|
55
|
+
redis (>= 3.3.4, < 5)
|
59
56
|
sinatra (2.0.0)
|
60
57
|
mustermann (~> 1.0)
|
61
58
|
rack (~> 2.0)
|
@@ -64,7 +61,7 @@ GEM
|
|
64
61
|
thor (0.20.0)
|
65
62
|
thread_safe (0.3.6)
|
66
63
|
tilt (2.0.8)
|
67
|
-
tzinfo (1.2.
|
64
|
+
tzinfo (1.2.4)
|
68
65
|
thread_safe (~> 0.1)
|
69
66
|
|
70
67
|
PLATFORMS
|
@@ -72,9 +69,9 @@ PLATFORMS
|
|
72
69
|
|
73
70
|
DEPENDENCIES
|
74
71
|
oj (~> 3.3)
|
75
|
-
rack-test (~> 0.7
|
76
|
-
rspec (~> 3.6
|
77
|
-
rspec_junit_formatter (~> 0.3
|
72
|
+
rack-test (~> 0.7)
|
73
|
+
rspec (~> 3.6)
|
74
|
+
rspec_junit_formatter (~> 0.3)
|
78
75
|
stealth!
|
79
76
|
|
80
77
|
BUNDLED WITH
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.9.
|
1
|
+
0.9.6
|
data/lib/stealth/base.rb
CHANGED
@@ -50,7 +50,7 @@ module Stealth
|
|
50
50
|
services_config = YAML.load(ERB.new(services_yaml).result)
|
51
51
|
|
52
52
|
unless services_config.has_key?(env)
|
53
|
-
raise Stealth::Errors::ConfigurationError, "Could not find services.yml configuration for #{env} environment
|
53
|
+
raise Stealth::Errors::ConfigurationError, "Could not find services.yml configuration for #{env} environment"
|
54
54
|
end
|
55
55
|
|
56
56
|
Stealth::Configuration.new(services_config[env])
|
data/lib/stealth/controller.rb
CHANGED
@@ -27,7 +27,7 @@ module Stealth
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def route
|
30
|
-
raise(Stealth::Errors::ControllerRoutingNotImplemented, "Please implement `route` method in BotController
|
30
|
+
raise(Stealth::Errors::ControllerRoutingNotImplemented, "Please implement `route` method in BotController")
|
31
31
|
end
|
32
32
|
|
33
33
|
def send_replies
|
@@ -53,7 +53,7 @@ module Stealth
|
|
53
53
|
sleep_duration = Float(reply["duration"])
|
54
54
|
sleep(sleep_duration)
|
55
55
|
rescue ArgumentError, TypeError
|
56
|
-
raise(ArgumentError, 'Invalid duration specified. Duration must be a float
|
56
|
+
raise(ArgumentError, 'Invalid duration specified. Duration must be a float')
|
57
57
|
end
|
58
58
|
end
|
59
59
|
end
|
@@ -128,7 +128,7 @@ module Stealth
|
|
128
128
|
begin
|
129
129
|
Kernel.const_get("Stealth::Services::#{current_service.capitalize}::ReplyHandler")
|
130
130
|
rescue NameError
|
131
|
-
raise(Stealth::Errors::ServiceNotRecognized, "The service '#{current_service}' was not recognized
|
131
|
+
raise(Stealth::Errors::ServiceNotRecognized, "The service '#{current_service}' was not recognized")
|
132
132
|
end
|
133
133
|
end
|
134
134
|
|
@@ -136,7 +136,7 @@ module Stealth
|
|
136
136
|
begin
|
137
137
|
Kernel.const_get("Stealth::Services::#{current_service.capitalize}::Client")
|
138
138
|
rescue NameError
|
139
|
-
raise(Stealth::Errors::ServiceNotRecognized, "The service '#{current_service}' was not recognized
|
139
|
+
raise(Stealth::Errors::ServiceNotRecognized, "The service '#{current_service}' was not recognized")
|
140
140
|
end
|
141
141
|
end
|
142
142
|
|
@@ -150,7 +150,7 @@ module Stealth
|
|
150
150
|
begin
|
151
151
|
File.read(reply_file_path)
|
152
152
|
rescue Errno::ENOENT
|
153
|
-
raise(Stealth::Errors::ReplyNotFound, "Could not find a reply in #{reply_file_path}
|
153
|
+
raise(Stealth::Errors::ReplyNotFound, "Could not find a reply in #{reply_file_path}")
|
154
154
|
end
|
155
155
|
end
|
156
156
|
|
@@ -168,7 +168,7 @@ module Stealth
|
|
168
168
|
|
169
169
|
def get_flow_and_state(session: nil, flow: nil, state: nil)
|
170
170
|
if session.nil? && flow.nil? && state.nil?
|
171
|
-
raise(ArgumentError, "A session, flow, or state must be specified
|
171
|
+
raise(ArgumentError, "A session, flow, or state must be specified")
|
172
172
|
end
|
173
173
|
|
174
174
|
if session.present?
|
@@ -195,7 +195,7 @@ module Stealth
|
|
195
195
|
if next_state.nil?
|
196
196
|
raise(
|
197
197
|
Stealth::Errors::InvalidStateTransitions,
|
198
|
-
"The next state after #{current_session.state_string} has not yet been defined
|
198
|
+
"The next state after #{current_session.state_string} has not yet been defined"
|
199
199
|
)
|
200
200
|
end
|
201
201
|
|
data/lib/stealth/dispatcher.rb
CHANGED
@@ -40,7 +40,7 @@ module Stealth
|
|
40
40
|
begin
|
41
41
|
Kernel.const_get("Stealth::Services::#{service.capitalize}::MessageHandler")
|
42
42
|
rescue NameError
|
43
|
-
raise(Stealth::Errors::ServiceNotRecognized, "The service '#{service}' was not recognized
|
43
|
+
raise(Stealth::Errors::ServiceNotRecognized, "The service '#{service}' was not recognized")
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
data/lib/stealth/flow/base.rb
CHANGED
@@ -168,7 +168,7 @@ module Stealth
|
|
168
168
|
# Reported by Kyle Burton
|
169
169
|
if !spec.states[event.transitions_to]
|
170
170
|
raise StealthFlowError.new("Event[#{event.name}]'s " +
|
171
|
-
"transitions_to[#{event.transitions_to}] is not a declared state
|
171
|
+
"transitions_to[#{event.transitions_to}] is not a declared state")
|
172
172
|
end
|
173
173
|
end
|
174
174
|
|
data/lib/stealth/server.rb
CHANGED
@@ -17,7 +17,7 @@ module Stealth
|
|
17
17
|
end
|
18
18
|
|
19
19
|
get_or_post '/incoming/:service' do
|
20
|
-
Stealth::Logger.l(topic: "incoming", message: "Received webhook from #{params[:service]}
|
20
|
+
Stealth::Logger.l(topic: "incoming", message: "Received webhook from #{params[:service]}")
|
21
21
|
|
22
22
|
# JSON params need to be parsed and added to the params
|
23
23
|
if request.env['CONTENT_TYPE'] == 'application/json'
|
@@ -17,12 +17,9 @@ module Stealth
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def transmit
|
20
|
-
raise(Stealth::Errors::ServiceImpaired, "Service implementation does not implement 'transmit'
|
20
|
+
raise(Stealth::Errors::ServiceImpaired, "Service implementation does not implement 'transmit'")
|
21
21
|
end
|
22
22
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
end
|
26
|
-
|
27
|
-
|
28
|
-
require 'stealth/services/facebook/client'
|
@@ -14,7 +14,7 @@ module Stealth
|
|
14
14
|
|
15
15
|
# Should respond with a Rack response (https://github.com/sinatra/sinatra#return-values)
|
16
16
|
def coordinate
|
17
|
-
raise(Stealth::Errors::ServiceImpaired, "Service request handler does not implement 'process'
|
17
|
+
raise(Stealth::Errors::ServiceImpaired, "Service request handler does not implement 'process'")
|
18
18
|
end
|
19
19
|
|
20
20
|
# After coordinate responds to the service, an optional async job
|
data/lib/stealth/session.rb
CHANGED
@@ -12,7 +12,7 @@ module Stealth
|
|
12
12
|
@user_id = user_id
|
13
13
|
|
14
14
|
unless defined?($redis)
|
15
|
-
raise(Stealth::Errors::RedisNotConfigured, "Please make sure REDIS_URL is configured before using sessions
|
15
|
+
raise(Stealth::Errors::RedisNotConfigured, "Please make sure REDIS_URL is configured before using sessions")
|
16
16
|
end
|
17
17
|
|
18
18
|
get
|
data/stealth.gemspec
CHANGED
@@ -16,7 +16,6 @@ Gem::Specification.new do |s|
|
|
16
16
|
s.add_dependency 'puma', '~> 3.10'
|
17
17
|
s.add_dependency 'thor', '~> 0.20'
|
18
18
|
s.add_dependency 'multi_json', '~> 1.12'
|
19
|
-
s.add_dependency 'faraday', '~> 0.13'
|
20
19
|
s.add_dependency 'sidekiq', '~> 5.0'
|
21
20
|
s.add_dependency 'activesupport', '~> 5.1'
|
22
21
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stealth
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mauricio Gomes
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-11-
|
11
|
+
date: 2017-11-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sinatra
|
@@ -66,20 +66,6 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '1.12'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: faraday
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '0.13'
|
76
|
-
type: :runtime
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - "~>"
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '0.13'
|
83
69
|
- !ruby/object:Gem::Dependency
|
84
70
|
name: sidekiq
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -192,12 +178,6 @@ files:
|
|
192
178
|
- lib/stealth/services/base_client.rb
|
193
179
|
- lib/stealth/services/base_message_handler.rb
|
194
180
|
- lib/stealth/services/base_reply_handler.rb
|
195
|
-
- lib/stealth/services/facebook/client.rb
|
196
|
-
- lib/stealth/services/facebook/events/message_event.rb
|
197
|
-
- lib/stealth/services/facebook/events/postback_event.rb
|
198
|
-
- lib/stealth/services/facebook/message_handler.rb
|
199
|
-
- lib/stealth/services/facebook/reply_handler.rb
|
200
|
-
- lib/stealth/services/facebook/setup.rb
|
201
181
|
- lib/stealth/services/jobs/handle_message_job.rb
|
202
182
|
- lib/stealth/session.rb
|
203
183
|
- lib/stealth/version.rb
|
@@ -232,7 +212,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
232
212
|
version: '0'
|
233
213
|
requirements: []
|
234
214
|
rubyforge_project:
|
235
|
-
rubygems_version: 2.6.
|
215
|
+
rubygems_version: 2.6.12
|
236
216
|
signing_key:
|
237
217
|
specification_version: 4
|
238
218
|
summary: Ruby framework for conversational bots
|
@@ -1,60 +0,0 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require 'faraday'
|
5
|
-
|
6
|
-
require 'stealth/services/facebook/message_handler'
|
7
|
-
require 'stealth/services/facebook/reply_handler'
|
8
|
-
require 'stealth/services/facebook/setup'
|
9
|
-
|
10
|
-
module Stealth
|
11
|
-
module Services
|
12
|
-
module Facebook
|
13
|
-
|
14
|
-
class Client < Stealth::Services::BaseClient
|
15
|
-
FB_ENDPOINT = "https://graph.facebook.com/v2.10/me"
|
16
|
-
|
17
|
-
attr_reader :api_endpoint, :reply
|
18
|
-
|
19
|
-
def initialize(reply:, endpoint: 'messages')
|
20
|
-
@reply = reply
|
21
|
-
access_token = "access_token=#{Stealth.config.facebook.page_access_token}"
|
22
|
-
@api_endpoint = [[FB_ENDPOINT, endpoint].join('/'), access_token].join('?')
|
23
|
-
end
|
24
|
-
|
25
|
-
def transmit
|
26
|
-
headers = { "Content-Type" => "application/json" }
|
27
|
-
response = Faraday.post(api_endpoint, reply.to_json, headers)
|
28
|
-
Stealth::Logger.l(topic: "facebook", message: "Transmitting. Response: #{response.status}: #{response.body}")
|
29
|
-
end
|
30
|
-
|
31
|
-
def self.fetch_profile(recipient_id:, fields: nil)
|
32
|
-
if fields.blank?
|
33
|
-
fields = [:first_name, :last_name, :profile_pic, :locale, :timezone, :gender, :is_payment_enabled, :last_ad_referral]
|
34
|
-
end
|
35
|
-
|
36
|
-
query_hash ={
|
37
|
-
fields: fields.join(','),
|
38
|
-
access_token: Stealth.config.facebook.page_access_token
|
39
|
-
}
|
40
|
-
|
41
|
-
uri = URI::HTTPS.build(
|
42
|
-
host: "graph.facebook.com",
|
43
|
-
path: "/v2.10/#{recipient_id}",
|
44
|
-
query: query_hash.to_query
|
45
|
-
)
|
46
|
-
|
47
|
-
response = Faraday.get(uri.to_s)
|
48
|
-
Stealth::Logger.l(topic: "facebook", message: "Requested user profile for #{recipient_id}. Response: #{response.status}: #{response.body}")
|
49
|
-
|
50
|
-
if response.status.in?(200..299)
|
51
|
-
MultiJson.load(response.body)
|
52
|
-
else
|
53
|
-
false
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
@@ -1,59 +0,0 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
module Stealth
|
5
|
-
module Services
|
6
|
-
module Facebook
|
7
|
-
|
8
|
-
class MessageEvent
|
9
|
-
|
10
|
-
attr_reader :service_message, :params
|
11
|
-
|
12
|
-
def initialize(service_message:, params:)
|
13
|
-
@service_message = service_message
|
14
|
-
@params = params
|
15
|
-
end
|
16
|
-
|
17
|
-
def process
|
18
|
-
fetch_message
|
19
|
-
fetch_location
|
20
|
-
fetch_attachments
|
21
|
-
end
|
22
|
-
|
23
|
-
private
|
24
|
-
|
25
|
-
def fetch_message
|
26
|
-
if params['message']['quick_reply'].present?
|
27
|
-
service_message.message = params['message']['quick_reply']['payload']
|
28
|
-
elsif params['message']['text'].present?
|
29
|
-
service_message.message = params['message']['text']
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def fetch_location
|
34
|
-
if params['location'].present?
|
35
|
-
lat = params['location']['coordinates']['lat']
|
36
|
-
lng = params['location']['coordinates']['long']
|
37
|
-
service_message.location = {
|
38
|
-
lat: lat,
|
39
|
-
lng: lng
|
40
|
-
}
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def fetch_attachments
|
45
|
-
if params['attachments'].present? && params['attachments'].is_a?(Array)
|
46
|
-
params['attachments'].each do |attachment|
|
47
|
-
service_message.attachments << {
|
48
|
-
type: attachment['type'],
|
49
|
-
url: attachment['payload']['url']
|
50
|
-
}
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
end
|
56
|
-
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
@@ -1,36 +0,0 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
module Stealth
|
5
|
-
module Services
|
6
|
-
module Facebook
|
7
|
-
|
8
|
-
class PostbackEvent
|
9
|
-
|
10
|
-
attr_reader :service_message, :params
|
11
|
-
|
12
|
-
def initialize(service_message:, params:)
|
13
|
-
@service_message = service_message
|
14
|
-
@params = params
|
15
|
-
end
|
16
|
-
|
17
|
-
def process
|
18
|
-
fetch_payload
|
19
|
-
fetch_referral
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
23
|
-
|
24
|
-
def fetch_payload
|
25
|
-
service_message.payload = params['postback']['payload']
|
26
|
-
end
|
27
|
-
|
28
|
-
def fetch_referral
|
29
|
-
service_message.referral = params['postback']['referral']
|
30
|
-
end
|
31
|
-
|
32
|
-
end
|
33
|
-
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
@@ -1,84 +0,0 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require 'stealth/services/facebook/events/message_event'
|
5
|
-
require 'stealth/services/facebook/events/postback_event'
|
6
|
-
|
7
|
-
module Stealth
|
8
|
-
module Services
|
9
|
-
module Facebook
|
10
|
-
|
11
|
-
class MessageHandler < Stealth::Services::BaseMessageHandler
|
12
|
-
|
13
|
-
attr_reader :service_message, :params, :headers, :facebook_message
|
14
|
-
|
15
|
-
def initialize(params:, headers:)
|
16
|
-
@params = params
|
17
|
-
@headers = headers
|
18
|
-
end
|
19
|
-
|
20
|
-
def coordinate
|
21
|
-
if facebook_is_validating_webhook?
|
22
|
-
respond_with_validation
|
23
|
-
else
|
24
|
-
# Queue the request processing so we can respond quickly to FB
|
25
|
-
# and also keep track of this message
|
26
|
-
Stealth::Services::HandleMessageJob.perform_async('facebook', params, {})
|
27
|
-
|
28
|
-
# Relay our acceptance
|
29
|
-
[200, 'OK']
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def process
|
34
|
-
@service_message = ServiceMessage.new(service: 'facebook')
|
35
|
-
@facebook_message = params['entry'].first['messaging'].first
|
36
|
-
service_message.sender_id = get_sender_id
|
37
|
-
service_message.timestamp = get_timestamp
|
38
|
-
process_facebook_event
|
39
|
-
|
40
|
-
service_message
|
41
|
-
end
|
42
|
-
|
43
|
-
private
|
44
|
-
|
45
|
-
def facebook_is_validating_webhook?
|
46
|
-
params['hub.verify_token'].present?
|
47
|
-
end
|
48
|
-
|
49
|
-
def respond_with_validation
|
50
|
-
if params['hub.verify_token'] == Stealth.config.facebook.verify_token
|
51
|
-
[200, params['hub.challenge']]
|
52
|
-
else
|
53
|
-
[401, "Verify token did not match environment variable."]
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def get_sender_id
|
58
|
-
facebook_message['sender']['id']
|
59
|
-
end
|
60
|
-
|
61
|
-
def get_timestamp
|
62
|
-
Time.at(facebook_message['timestamp']).to_datetime
|
63
|
-
end
|
64
|
-
|
65
|
-
def process_facebook_event
|
66
|
-
if facebook_message['message'].present?
|
67
|
-
message_event = Stealth::Services::Facebook::MessageEvent.new(
|
68
|
-
service_message: service_message,
|
69
|
-
params: facebook_message
|
70
|
-
)
|
71
|
-
elsif facebook_message['postback'].present?
|
72
|
-
message_event = Stealth::Services::Facebook::PostbackEvent.new(
|
73
|
-
service_message: service_message,
|
74
|
-
params: facebook_message
|
75
|
-
)
|
76
|
-
end
|
77
|
-
|
78
|
-
message_event.process
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
@@ -1,480 +0,0 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
module Stealth
|
5
|
-
module Services
|
6
|
-
module Facebook
|
7
|
-
|
8
|
-
class ReplyHandler < Stealth::Services::BaseReplyHandler
|
9
|
-
|
10
|
-
attr_reader :recipient_id, :reply
|
11
|
-
|
12
|
-
def initialize(recipient_id: nil, reply: nil)
|
13
|
-
@recipient_id = recipient_id
|
14
|
-
@reply = reply
|
15
|
-
end
|
16
|
-
|
17
|
-
def text
|
18
|
-
check_if_arguments_are_valid!(
|
19
|
-
suggestions: reply['suggestions'],
|
20
|
-
buttons: reply['buttons']
|
21
|
-
)
|
22
|
-
|
23
|
-
template = unstructured_template
|
24
|
-
template['message']['text'] = reply['text']
|
25
|
-
|
26
|
-
if reply['suggestions'].present?
|
27
|
-
fb_suggestions = generate_suggestions(suggestions: reply['suggestions'])
|
28
|
-
template["message"]["quick_replies"] = fb_suggestions
|
29
|
-
end
|
30
|
-
|
31
|
-
# If buttons are present, we need to convert this to a button template
|
32
|
-
if reply['buttons'].present?
|
33
|
-
template['message'].delete('text')
|
34
|
-
|
35
|
-
fb_buttons = generate_buttons(buttons: reply['buttons'])
|
36
|
-
attachment = button_attachment_template(text: reply['text'], buttons: fb_buttons)
|
37
|
-
template['message']['attachment'] = attachment
|
38
|
-
end
|
39
|
-
|
40
|
-
template
|
41
|
-
end
|
42
|
-
|
43
|
-
def image
|
44
|
-
check_if_arguments_are_valid!(
|
45
|
-
suggestions: reply['suggestions'],
|
46
|
-
buttons: reply['buttons']
|
47
|
-
)
|
48
|
-
|
49
|
-
template = unstructured_template
|
50
|
-
attachment = attachment_template(
|
51
|
-
attachment_type: 'image',
|
52
|
-
attachment_url: reply['image_url']
|
53
|
-
)
|
54
|
-
template['message']['attachment'] = attachment
|
55
|
-
|
56
|
-
if reply['suggestions'].present?
|
57
|
-
fb_suggestions = generate_suggestions(suggestions: reply['suggestions'])
|
58
|
-
template["message"]["quick_replies"] = fb_suggestions
|
59
|
-
end
|
60
|
-
|
61
|
-
template
|
62
|
-
end
|
63
|
-
|
64
|
-
def audio
|
65
|
-
check_if_arguments_are_valid!(
|
66
|
-
suggestions: reply['suggestions'],
|
67
|
-
buttons: reply['buttons']
|
68
|
-
)
|
69
|
-
|
70
|
-
template = unstructured_template
|
71
|
-
attachment = attachment_template(
|
72
|
-
attachment_type: 'audio',
|
73
|
-
attachment_url: reply['audio_url']
|
74
|
-
)
|
75
|
-
template['message']['attachment'] = attachment
|
76
|
-
|
77
|
-
if reply['suggestions'].present?
|
78
|
-
fb_suggestions = generate_suggestions(suggestions: reply['suggestions'])
|
79
|
-
template["message"]["quick_replies"] = fb_suggestions
|
80
|
-
end
|
81
|
-
|
82
|
-
template
|
83
|
-
end
|
84
|
-
|
85
|
-
def video
|
86
|
-
check_if_arguments_are_valid!(
|
87
|
-
suggestions: reply['suggestions'],
|
88
|
-
buttons: reply['buttons']
|
89
|
-
)
|
90
|
-
|
91
|
-
template = unstructured_template
|
92
|
-
attachment = attachment_template(
|
93
|
-
attachment_type: 'video',
|
94
|
-
attachment_url: reply['video_url']
|
95
|
-
)
|
96
|
-
template['message']['attachment'] = attachment
|
97
|
-
|
98
|
-
if reply['suggestions'].present?
|
99
|
-
fb_suggestions = generate_suggestions(suggestions: reply['suggestions'])
|
100
|
-
template["message"]["quick_replies"] = fb_suggestions
|
101
|
-
end
|
102
|
-
|
103
|
-
template
|
104
|
-
end
|
105
|
-
|
106
|
-
def file
|
107
|
-
check_if_arguments_are_valid!(
|
108
|
-
suggestions: reply['suggestions'],
|
109
|
-
buttons: reply['buttons']
|
110
|
-
)
|
111
|
-
|
112
|
-
template = unstructured_template
|
113
|
-
attachment = attachment_template(
|
114
|
-
attachment_type: 'file',
|
115
|
-
attachment_url: reply['file_url']
|
116
|
-
)
|
117
|
-
template['message']['attachment'] = attachment
|
118
|
-
|
119
|
-
if reply['suggestions'].present?
|
120
|
-
fb_suggestions = generate_suggestions(suggestions: reply['suggestions'])
|
121
|
-
template["message"]["quick_replies"] = fb_suggestions
|
122
|
-
end
|
123
|
-
|
124
|
-
template
|
125
|
-
end
|
126
|
-
|
127
|
-
def cards
|
128
|
-
template = card_template(
|
129
|
-
sharable: reply["sharable"],
|
130
|
-
aspect_ratio: reply["aspect_ratio"]
|
131
|
-
)
|
132
|
-
|
133
|
-
fb_elements = generate_card_elements(elements: reply["elements"])
|
134
|
-
template["message"]["attachment"]["payload"]["elements"] = fb_elements
|
135
|
-
|
136
|
-
if reply['suggestions'].present?
|
137
|
-
fb_suggestions = generate_suggestions(suggestions: reply['suggestions'])
|
138
|
-
template["message"]["quick_replies"] = fb_suggestions
|
139
|
-
end
|
140
|
-
|
141
|
-
template
|
142
|
-
end
|
143
|
-
|
144
|
-
def list
|
145
|
-
template = list_template(
|
146
|
-
top_element_style: reply["top_element_style"]
|
147
|
-
)
|
148
|
-
|
149
|
-
fb_elements = generate_list_elements(elements: reply["elements"])
|
150
|
-
template["message"]["attachments"]["payload"]["elements"] = fb_elements
|
151
|
-
|
152
|
-
if reply["buttons"].present?
|
153
|
-
if reply["buttons"].size > 1
|
154
|
-
raise(ArgumentError, "Facebook lists support a single button attached to the list itsef.")
|
155
|
-
end
|
156
|
-
|
157
|
-
template["message"]["attachments"]["payload"]["buttons"] = generate_buttons(buttons: reply["buttons"])
|
158
|
-
end
|
159
|
-
|
160
|
-
template
|
161
|
-
end
|
162
|
-
|
163
|
-
def mark_seen
|
164
|
-
sender_action_template(action: 'mark_seen')
|
165
|
-
end
|
166
|
-
|
167
|
-
def enable_typing_indicator
|
168
|
-
sender_action_template(action: 'typing_on')
|
169
|
-
end
|
170
|
-
|
171
|
-
def disable_typing_indicator
|
172
|
-
sender_action_template(action: 'typing_off')
|
173
|
-
end
|
174
|
-
|
175
|
-
def delay
|
176
|
-
enable_typing_indicator
|
177
|
-
end
|
178
|
-
|
179
|
-
# generates property/value pairs required to set the profile
|
180
|
-
def messenger_profile
|
181
|
-
unless Stealth.config.facebook.setup.present?
|
182
|
-
raise Stealth::Errors::ConfigurationError, "Setup for Facebook is not specified in services.yml."
|
183
|
-
end
|
184
|
-
|
185
|
-
profile = {}
|
186
|
-
Stealth.config.facebook.setup.each do |profile_option, _|
|
187
|
-
profile[profile_option] = self.send(profile_option)
|
188
|
-
end
|
189
|
-
|
190
|
-
profile
|
191
|
-
end
|
192
|
-
|
193
|
-
private
|
194
|
-
|
195
|
-
def unstructured_template
|
196
|
-
{
|
197
|
-
"recipient" => {
|
198
|
-
"id" => recipient_id
|
199
|
-
},
|
200
|
-
"message" => { }
|
201
|
-
}
|
202
|
-
end
|
203
|
-
|
204
|
-
def card_template(sharable: nil, aspect_ratio: nil)
|
205
|
-
template = {
|
206
|
-
"recipient" => {
|
207
|
-
"id" => recipient_id
|
208
|
-
},
|
209
|
-
"message" => {
|
210
|
-
"attachment" => {
|
211
|
-
"type" => "template",
|
212
|
-
"payload" => {
|
213
|
-
"template_type" => "generic",
|
214
|
-
"elements" => []
|
215
|
-
}
|
216
|
-
}
|
217
|
-
}
|
218
|
-
}
|
219
|
-
|
220
|
-
if sharable.present?
|
221
|
-
template["message"]["payload"]["sharable"] = sharable
|
222
|
-
end
|
223
|
-
|
224
|
-
if aspect_ratio.present?
|
225
|
-
template["message"]["payload"]["image_aspect_ratio"] = aspect_ratio
|
226
|
-
end
|
227
|
-
|
228
|
-
template
|
229
|
-
end
|
230
|
-
|
231
|
-
def list_template(top_element_style: nil, buttons: [])
|
232
|
-
template = {
|
233
|
-
"recipient" => {
|
234
|
-
"id" => recipient_id
|
235
|
-
},
|
236
|
-
"message" => {
|
237
|
-
"attachment" => {
|
238
|
-
"type" => "template",
|
239
|
-
"payload" => {
|
240
|
-
"template_type" => "list",
|
241
|
-
"elements" => []
|
242
|
-
}
|
243
|
-
}
|
244
|
-
}
|
245
|
-
}
|
246
|
-
|
247
|
-
if top_element_style.present?
|
248
|
-
unless ['large', 'compact'].include?(top_element_style)
|
249
|
-
raise(ArgumentError, "Facebook list replies only support 'large' or 'compact' as the top_element_style.")
|
250
|
-
end
|
251
|
-
|
252
|
-
template["message"]["payload"]["top_element_style"] = top_element_style
|
253
|
-
end
|
254
|
-
|
255
|
-
if buttons.present?
|
256
|
-
unless buttons.size > 1
|
257
|
-
raise(ArgumentError, "Facebook lists only support a single button in the top element.")
|
258
|
-
end
|
259
|
-
|
260
|
-
template["message"]["payload"]["buttons"] = aspect_ratio
|
261
|
-
end
|
262
|
-
|
263
|
-
template
|
264
|
-
end
|
265
|
-
|
266
|
-
def element_template(element_type:, element:)
|
267
|
-
unless element["title"].present?
|
268
|
-
raise(ArgumentError, "Facebook card and list elements must have a 'title' attribute.")
|
269
|
-
end
|
270
|
-
|
271
|
-
template = {
|
272
|
-
"title" => element["title"]
|
273
|
-
}
|
274
|
-
|
275
|
-
if element["subtitle"].present?
|
276
|
-
template["subtitle"] = element["subtitle"]
|
277
|
-
end
|
278
|
-
|
279
|
-
if element["image_url"].present?
|
280
|
-
template["image_url"] = element["image_url"]
|
281
|
-
end
|
282
|
-
|
283
|
-
if element["default_action"].present?
|
284
|
-
default_action = generate_default_action(action_params: element["default_action"])
|
285
|
-
template["default_action"] = default_action
|
286
|
-
end
|
287
|
-
|
288
|
-
if element["buttons"].present?
|
289
|
-
if element_type == 'card' && element["buttons"].size > 3
|
290
|
-
raise(ArgumentError, "Facebook card elements only support 3 buttons.")
|
291
|
-
end
|
292
|
-
|
293
|
-
if element_type == 'list' && element["buttons"].size > 1
|
294
|
-
raise(ArgumentError, "Facebook list elements only support 1 button.")
|
295
|
-
end
|
296
|
-
|
297
|
-
fb_buttons = generate_buttons(buttons: element["buttons"])
|
298
|
-
template["buttons"] = fb_buttons
|
299
|
-
end
|
300
|
-
|
301
|
-
template
|
302
|
-
end
|
303
|
-
|
304
|
-
def attachment_template(attachment_type:, attachment_url:)
|
305
|
-
{
|
306
|
-
"type" => attachment_type,
|
307
|
-
"payload" => {
|
308
|
-
"url" => attachment_url
|
309
|
-
}
|
310
|
-
}
|
311
|
-
end
|
312
|
-
|
313
|
-
def button_attachment_template(text:, buttons:)
|
314
|
-
{
|
315
|
-
"type" => "template",
|
316
|
-
"payload" => {
|
317
|
-
"template_type" => "button",
|
318
|
-
"text" => text,
|
319
|
-
"buttons" => buttons
|
320
|
-
}
|
321
|
-
}
|
322
|
-
end
|
323
|
-
|
324
|
-
def sender_action_template(action:)
|
325
|
-
{
|
326
|
-
"recipient" => {
|
327
|
-
"id" => recipient_id
|
328
|
-
},
|
329
|
-
"sender_action" => action
|
330
|
-
}
|
331
|
-
end
|
332
|
-
|
333
|
-
def generate_card_elements(elements:)
|
334
|
-
if elements.size > 10
|
335
|
-
raise(ArgumentError, "Facebook cards can have at most 10 cards.")
|
336
|
-
end
|
337
|
-
|
338
|
-
fb_elements = elements.collect do |element|
|
339
|
-
element_template(element_type: 'card', element: element)
|
340
|
-
end
|
341
|
-
|
342
|
-
fb_elements
|
343
|
-
end
|
344
|
-
|
345
|
-
def generate_list_elements(elements:)
|
346
|
-
if elements.size < 2 || elements.size > 4
|
347
|
-
raise(ArgumentError, "Facebook lists must have 2-4 elements.")
|
348
|
-
end
|
349
|
-
|
350
|
-
fb_elements = elements.collect do |element|
|
351
|
-
element_template(element_type: 'list', element: element)
|
352
|
-
end
|
353
|
-
|
354
|
-
fb_elements
|
355
|
-
end
|
356
|
-
|
357
|
-
def generate_suggestions(suggestions:)
|
358
|
-
quick_replies = suggestions.collect do |suggestion|
|
359
|
-
# If the user selected a location-type button, no other info needed
|
360
|
-
if suggestion["type"] == 'location'
|
361
|
-
quick_reply = { "content_type" => "location" }
|
362
|
-
|
363
|
-
# Facebook only supports these two types for now
|
364
|
-
else
|
365
|
-
quick_reply = {
|
366
|
-
"content_type" => "text",
|
367
|
-
"title" => suggestion["text"]
|
368
|
-
}
|
369
|
-
|
370
|
-
if suggestion["payload"].present?
|
371
|
-
quick_reply["payload"] = suggestion["payload"]
|
372
|
-
else
|
373
|
-
quick_reply["payload"] = suggestion["text"]
|
374
|
-
end
|
375
|
-
|
376
|
-
if suggestion["image_url"].present?
|
377
|
-
quick_reply["image_url"] = suggestion["image_url"]
|
378
|
-
end
|
379
|
-
end
|
380
|
-
|
381
|
-
quick_reply
|
382
|
-
end
|
383
|
-
|
384
|
-
quick_replies
|
385
|
-
end
|
386
|
-
|
387
|
-
# Requires adding support for Buy, Log In, Log Out, and Share button types
|
388
|
-
def generate_buttons(buttons:)
|
389
|
-
fb_buttons = buttons.collect do |button|
|
390
|
-
case button['type']
|
391
|
-
when 'url'
|
392
|
-
button = {
|
393
|
-
"type" => "web_url",
|
394
|
-
"url" => button["url"],
|
395
|
-
"title" => button["text"]
|
396
|
-
}
|
397
|
-
|
398
|
-
if button["webview_height"].present?
|
399
|
-
button["webview_height_ratio"] = button["webview_height"]
|
400
|
-
end
|
401
|
-
|
402
|
-
button
|
403
|
-
|
404
|
-
when 'payload'
|
405
|
-
button = {
|
406
|
-
"type" => "postback",
|
407
|
-
"payload" => button["payload"],
|
408
|
-
"title" => button["text"]
|
409
|
-
}
|
410
|
-
|
411
|
-
when 'call'
|
412
|
-
button = {
|
413
|
-
"type" => "phone_number",
|
414
|
-
"payload" => button["phone_number"],
|
415
|
-
"title" => button["text"]
|
416
|
-
}
|
417
|
-
|
418
|
-
when 'nested'
|
419
|
-
button = {
|
420
|
-
"type" => "nested",
|
421
|
-
"title" => button["text"],
|
422
|
-
"call_to_actions" => generate_buttons(buttons: button["buttons"])
|
423
|
-
}
|
424
|
-
|
425
|
-
else
|
426
|
-
raise(Stealth::Errors::ServiceImpaired, "Sorry, we don't yet support #{button["type"]} buttons yet!")
|
427
|
-
end
|
428
|
-
|
429
|
-
button
|
430
|
-
end
|
431
|
-
|
432
|
-
fb_buttons
|
433
|
-
end
|
434
|
-
|
435
|
-
def generate_default_action(action_params:)
|
436
|
-
default_action = {
|
437
|
-
"type" => "web_url",
|
438
|
-
"url" => action_params["url"]
|
439
|
-
}
|
440
|
-
|
441
|
-
if action_params["webview_height"].present?
|
442
|
-
action_params["webview_height_ratio"] = action_params["webview_height"]
|
443
|
-
end
|
444
|
-
|
445
|
-
default_action
|
446
|
-
end
|
447
|
-
|
448
|
-
def check_if_arguments_are_valid!(suggestions:, buttons:)
|
449
|
-
if suggestions.present? && buttons.present?
|
450
|
-
raise(ArgumentError, "A reply cannot have buttons and suggestions!")
|
451
|
-
end
|
452
|
-
end
|
453
|
-
|
454
|
-
def greeting
|
455
|
-
Stealth.config.facebook.setup.greeting.map do |greeting|
|
456
|
-
{
|
457
|
-
"locale" => greeting["locale"],
|
458
|
-
"text" => greeting["text"]
|
459
|
-
}
|
460
|
-
end
|
461
|
-
end
|
462
|
-
|
463
|
-
def persistent_menu
|
464
|
-
Stealth.config.facebook.setup.persistent_menu.map do |persistent_menu|
|
465
|
-
{
|
466
|
-
"locale" => persistent_menu['locale'],
|
467
|
-
"composer_input_disabled" => (persistent_menu['composer_input_disabled'] || false),
|
468
|
-
"call_to_actions" => generate_buttons(buttons: persistent_menu['call_to_actions'])
|
469
|
-
}
|
470
|
-
end
|
471
|
-
end
|
472
|
-
|
473
|
-
def get_started
|
474
|
-
Stealth.config.facebook.setup.get_started
|
475
|
-
end
|
476
|
-
end
|
477
|
-
|
478
|
-
end
|
479
|
-
end
|
480
|
-
end
|
@@ -1,25 +0,0 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require 'stealth/services/facebook/client'
|
5
|
-
|
6
|
-
module Stealth
|
7
|
-
module Services
|
8
|
-
module Facebook
|
9
|
-
|
10
|
-
class Setup
|
11
|
-
|
12
|
-
class << self
|
13
|
-
def trigger
|
14
|
-
reply_handler = Stealth::Services::Facebook::ReplyHandler.new
|
15
|
-
reply = reply_handler.messenger_profile
|
16
|
-
client = Stealth::Services::Facebook::Client.new(reply: reply, endpoint: 'messenger_profile')
|
17
|
-
client.transmit
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
end
|
22
|
-
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|