bobot 1.0.52
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +66 -0
- data/Rakefile +19 -0
- data/app/controllers/bobot/application_controller.rb +5 -0
- data/app/controllers/bobot/webhook_controller.rb +76 -0
- data/app/jobs/bobot/application_job.rb +4 -0
- data/app/jobs/bobot/commander_job.rb +9 -0
- data/app/jobs/bobot/deliver_job.rb +16 -0
- data/app/mailers/bobot/application_mailer.rb +6 -0
- data/app/models/bobot/application_record.rb +5 -0
- data/config/locales/bobot.en.yml +28 -0
- data/config/routes.rb +6 -0
- data/lib/bobot.rb +18 -0
- data/lib/bobot/buttons.rb +168 -0
- data/lib/bobot/commander.rb +68 -0
- data/lib/bobot/configuration.rb +206 -0
- data/lib/bobot/engine.rb +33 -0
- data/lib/bobot/error_parser.rb +102 -0
- data/lib/bobot/event.rb +40 -0
- data/lib/bobot/events/account_linking.rb +15 -0
- data/lib/bobot/events/common.rb +170 -0
- data/lib/bobot/events/delivery.rb +20 -0
- data/lib/bobot/events/message.rb +72 -0
- data/lib/bobot/events/message_echo.rb +8 -0
- data/lib/bobot/events/optin.rb +11 -0
- data/lib/bobot/events/postback.rb +20 -0
- data/lib/bobot/events/read.rb +15 -0
- data/lib/bobot/events/referral.rb +33 -0
- data/lib/bobot/exceptions.rb +73 -0
- data/lib/bobot/graph_facebook.rb +90 -0
- data/lib/bobot/profile.rb +23 -0
- data/lib/bobot/subscription.rb +19 -0
- data/lib/bobot/user.rb +13 -0
- data/lib/bobot/version.rb +14 -0
- data/lib/generators/bobot/install_generator.rb +28 -0
- data/lib/generators/bobot/templates/app/bobot/message.rb +3 -0
- data/lib/generators/bobot/templates/app/bobot/postback.rb +22 -0
- data/lib/generators/bobot/templates/app/bobot/workflow.rb +17 -0
- data/lib/generators/bobot/templates/config/bobot.yml +39 -0
- data/lib/generators/bobot/templates/config/initializers/bobot.rb +30 -0
- data/lib/generators/bobot/templates/config/locales/bobot.en.yml +30 -0
- data/lib/generators/bobot/templates/config/locales/bobot.fr.yml +29 -0
- data/lib/generators/bobot/uninstall_generator.rb +24 -0
- data/lib/generators/bobot/utils.rb +30 -0
- data/lib/tasks/bobot_tasks.rake +11 -0
- data/spec/bobot/bobot_spec.rb +24 -0
- data/spec/bobot/event/account_linking_spec.rb +59 -0
- data/spec/bobot/event/common_spec.rb +259 -0
- data/spec/bobot/event/delivery_spec.rb +62 -0
- data/spec/bobot/event/message_echo_spec.rb +276 -0
- data/spec/bobot/event/message_spec.rb +276 -0
- data/spec/bobot/event/optin_spec.rb +50 -0
- data/spec/bobot/event/postback_spec.rb +94 -0
- data/spec/bobot/event/read_spec.rb +51 -0
- data/spec/bobot/event/referral_spec.rb +66 -0
- data/spec/bobot/event_spec.rb +167 -0
- data/spec/bobot/install_generator_spec.rb +43 -0
- data/spec/bobot/profile_spec.rb +170 -0
- data/spec/bobot/subscription_spec.rb +139 -0
- data/spec/bobot/user_spec.rb +91 -0
- data/spec/controllers/bobot/application_controller_spec.rb +4 -0
- data/spec/controllers/bobot/webhook_controller_spec.rb +5 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/config/manifest.js +3 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/bobot/workflow.rb +17 -0
- data/spec/dummy/app/controllers/application_controller.rb +2 -0
- data/spec/dummy/app/jobs/application_job.rb +2 -0
- data/spec/dummy/app/models/application_record.rb +3 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +35 -0
- data/spec/dummy/bin/update +29 -0
- data/spec/dummy/config.ru +5 -0
- data/spec/dummy/config/application.rb +30 -0
- data/spec/dummy/config/bobot.yml +27 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +19 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +42 -0
- data/spec/dummy/config/environments/production.rb +78 -0
- data/spec/dummy/config/environments/test.rb +38 -0
- data/spec/dummy/config/initializers/application_controller_renderer.rb +6 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/bobot.rb +30 -0
- data/spec/dummy/config/initializers/cors.rb +16 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/bobot.en.yml +28 -0
- data/spec/dummy/config/locales/bobot.fr.yml +27 -0
- data/spec/dummy/config/locales/en.yml +33 -0
- data/spec/dummy/config/puma.rb +56 -0
- data/spec/dummy/config/routes.rb +3 -0
- data/spec/dummy/config/secrets.yml +32 -0
- data/spec/dummy/config/spring.rb +6 -0
- data/spec/dummy/db/schema.rb +15 -0
- data/spec/examples.txt +111 -0
- data/spec/helpers/graph_api_helpers.rb +6 -0
- data/spec/jobs/bobot/commander_job_spec.rb +31 -0
- data/spec/lint/rubocop_spec.rb +8 -0
- data/spec/rails_helper.rb +67 -0
- data/spec/spec_helper.rb +105 -0
- data/spec/travis/database.travis.mysql.yml +19 -0
- metadata +251 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
module Bobot
|
2
|
+
module Event
|
3
|
+
class AccountLinking
|
4
|
+
include Bobot::Event::Common
|
5
|
+
|
6
|
+
def status
|
7
|
+
@messaging['account_linking']['status']
|
8
|
+
end
|
9
|
+
|
10
|
+
def authorization_code
|
11
|
+
@messaging['account_linking']['authorization_code']
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
module Bobot
|
2
|
+
module Event
|
3
|
+
# Common attributes for all incoming data from Facebook.
|
4
|
+
module Common
|
5
|
+
attr_reader :messaging
|
6
|
+
attr_accessor :delay_options
|
7
|
+
attr_accessor :payloads_sent
|
8
|
+
|
9
|
+
def initialize(messaging)
|
10
|
+
@messaging = messaging
|
11
|
+
@delay_options = { wait: 0, wait_until: nil }
|
12
|
+
@payloads_sent = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_delivery(payload:)
|
16
|
+
@payloads_sent << payload
|
17
|
+
end
|
18
|
+
|
19
|
+
def sender
|
20
|
+
@messaging['sender']
|
21
|
+
end
|
22
|
+
|
23
|
+
def recipient
|
24
|
+
@messaging['recipient']
|
25
|
+
end
|
26
|
+
|
27
|
+
def delay(wait: 0, wait_until: nil)
|
28
|
+
raise Bobot::FieldFormat.new('wait has to be positive integer.') unless wait.present?
|
29
|
+
if Bobot.async
|
30
|
+
@delay_options[:wait] = wait if wait >= 0
|
31
|
+
@delay_options[:wait_until] = wait_until if wait_until.present?
|
32
|
+
else
|
33
|
+
warn "delay is ignored since you configured Bobot.async to 'false'"
|
34
|
+
end
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def sent_at
|
39
|
+
Time.zone.at(@messaging['timestamp'] / 1000)
|
40
|
+
end
|
41
|
+
|
42
|
+
def deliver(payload_template:)
|
43
|
+
raise Bobot::FieldFormat.new('payload_template is required.') unless payload_template.present?
|
44
|
+
@payloads_sent << payload_template
|
45
|
+
job = Bobot::DeliverJob
|
46
|
+
if Bobot.async
|
47
|
+
job = job.set(wait: @delay_options[:wait]) if @delay_options[:wait] > 0
|
48
|
+
job = job.set(wait: @delay_options[:wait_until]) if @delay_options[:wait_until].present?
|
49
|
+
job.perform_later(sender: sender, access_token: access_token, payload_template: payload_template)
|
50
|
+
else
|
51
|
+
job.perform_now(sender: sender, access_token: access_token, payload_template: payload_template)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def sender_action(sender_action:)
|
56
|
+
deliver(payload_template: { sender_action: sender_action })
|
57
|
+
end
|
58
|
+
|
59
|
+
def show_typing(state:)
|
60
|
+
sender_action(sender_action: state ? 'typing_on' : 'typing_off')
|
61
|
+
end
|
62
|
+
|
63
|
+
def mark_as_seen
|
64
|
+
sender_action(sender_action: 'mark_seen')
|
65
|
+
end
|
66
|
+
|
67
|
+
def reply(payload_message:)
|
68
|
+
deliver(payload_template: { message: payload_message })
|
69
|
+
end
|
70
|
+
|
71
|
+
def reply_with_text(text:)
|
72
|
+
raise Bobot::FieldFormat.new('text is required.') unless text.present?
|
73
|
+
raise Bobot::FieldFormat.new('text length is limited to 640.') if text.size > 640
|
74
|
+
reply(
|
75
|
+
payload_message: {
|
76
|
+
text: text,
|
77
|
+
},
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
def reply_with_attachment(url:, type:)
|
82
|
+
raise Bobot::FieldFormat.new('url is required.') unless url.present?
|
83
|
+
raise Bobot::FieldFormat.new('type is required.') unless type.present?
|
84
|
+
raise Bobot::FieldFormat.new('type is invalid, only "image, audio, video, file" are permitted.') unless %w[image audio video file].include?(type)
|
85
|
+
reply(
|
86
|
+
payload_message: {
|
87
|
+
attachment: {
|
88
|
+
type: type,
|
89
|
+
payload: {
|
90
|
+
url: url,
|
91
|
+
}.tap { |properties| properties.merge!(is_reusable: true) if type == 'image' },
|
92
|
+
},
|
93
|
+
},
|
94
|
+
)
|
95
|
+
end
|
96
|
+
|
97
|
+
def reply_with_image(url:)
|
98
|
+
reply_with_attachment(url: url, type: 'image')
|
99
|
+
end
|
100
|
+
|
101
|
+
def reply_with_audio(url:)
|
102
|
+
reply_with_attachment(url: url, type: 'audio')
|
103
|
+
end
|
104
|
+
|
105
|
+
def reply_with_video(url:)
|
106
|
+
reply_with_attachment(url: url, type: 'video')
|
107
|
+
end
|
108
|
+
|
109
|
+
def reply_with_file(url:)
|
110
|
+
reply_with_attachment(url: url, type: 'file')
|
111
|
+
end
|
112
|
+
|
113
|
+
def reply_with_quick_replies(text:, quick_replies:)
|
114
|
+
raise Bobot::FieldFormat.new('text is required.') unless text.present?
|
115
|
+
raise Bobot::FieldFormat.new('text length is limited to 640.') if text.size > 640
|
116
|
+
raise Bobot::FieldFormat.new('quick_replies are required.') unless quick_replies.present?
|
117
|
+
raise Bobot::FieldFormat.new('quick_replies are limited to 11.') if quick_replies.size > 11
|
118
|
+
reply(
|
119
|
+
payload_message: {
|
120
|
+
text: text,
|
121
|
+
quick_replies: quick_replies,
|
122
|
+
},
|
123
|
+
)
|
124
|
+
end
|
125
|
+
|
126
|
+
def reply_with_buttons(text:, buttons:)
|
127
|
+
raise Bobot::FieldFormat.new('text is required.') unless text.present?
|
128
|
+
raise Bobot::FieldFormat.new('text length is limited to 640.') if text.size > 640
|
129
|
+
raise Bobot::FieldFormat.new('buttons are required.') unless buttons.present?
|
130
|
+
raise Bobot::FieldFormat.new('buttons are limited to 3.') if buttons.size > 3
|
131
|
+
reply(
|
132
|
+
payload_message: {
|
133
|
+
attachment: {
|
134
|
+
type: 'template',
|
135
|
+
payload: {
|
136
|
+
template_type: 'button',
|
137
|
+
text: text,
|
138
|
+
buttons: buttons,
|
139
|
+
},
|
140
|
+
},
|
141
|
+
},
|
142
|
+
)
|
143
|
+
end
|
144
|
+
|
145
|
+
def reply_with_generic(elements:, image_aspect_ratio: 'square')
|
146
|
+
raise Bobot::FieldFormat.new('elements are required.') if elements.nil?
|
147
|
+
raise Bobot::FieldFormat.new('elements are limited to 10.') if elements.size > 10
|
148
|
+
raise Bobot::FieldFormat.new('image_aspect_ratio is required.') if image_aspect_ratio.nil?
|
149
|
+
raise Bobot::FieldFormat.new('image_aspect_ratio is invalid, only "square, horizontal" are permitted.') unless %w[square horizontal].include?(image_aspect_ratio)
|
150
|
+
reply(
|
151
|
+
payload_message: {
|
152
|
+
attachment: {
|
153
|
+
type: 'template',
|
154
|
+
payload: {
|
155
|
+
template_type: 'generic',
|
156
|
+
image_aspect_ratio: image_aspect_ratio,
|
157
|
+
elements: elements,
|
158
|
+
},
|
159
|
+
},
|
160
|
+
},
|
161
|
+
)
|
162
|
+
end
|
163
|
+
alias_method :reply_with_carousel, :reply_with_generic
|
164
|
+
|
165
|
+
def access_token
|
166
|
+
Bobot.page_access_token
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Bobot
|
2
|
+
module Event
|
3
|
+
# The Delivery class represents the receipt of a delivered message.
|
4
|
+
class Delivery
|
5
|
+
include Bobot::Event::Common
|
6
|
+
|
7
|
+
def ids
|
8
|
+
@messaging['delivery']['mids']
|
9
|
+
end
|
10
|
+
|
11
|
+
def at
|
12
|
+
Time.zone.at(@messaging['delivery']['watermark'] / 1000)
|
13
|
+
end
|
14
|
+
|
15
|
+
def seq
|
16
|
+
@messaging['delivery']['seq']
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Bobot
|
2
|
+
module Event
|
3
|
+
class Message
|
4
|
+
include Bobot::Event::Common
|
5
|
+
|
6
|
+
ATTACHMENT_TYPES = %w[image audio video file location fallback].freeze
|
7
|
+
|
8
|
+
def id
|
9
|
+
@messaging['message']['mid']
|
10
|
+
end
|
11
|
+
|
12
|
+
def seq
|
13
|
+
@messaging['message']['seq']
|
14
|
+
end
|
15
|
+
|
16
|
+
def text
|
17
|
+
@messaging['message']['text']
|
18
|
+
end
|
19
|
+
|
20
|
+
def echo?
|
21
|
+
@messaging['message']['is_echo']
|
22
|
+
end
|
23
|
+
|
24
|
+
def attachments
|
25
|
+
@messaging['message']['attachments']
|
26
|
+
end
|
27
|
+
|
28
|
+
def app_id
|
29
|
+
@messaging['message']['app_id']
|
30
|
+
end
|
31
|
+
|
32
|
+
ATTACHMENT_TYPES.each do |attachment_type|
|
33
|
+
define_method "#{attachment_type}_attachment?" do
|
34
|
+
attachment_type?(attachment_type)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def attachment_type
|
39
|
+
return if attachments.nil?
|
40
|
+
|
41
|
+
attachments.first['type']
|
42
|
+
end
|
43
|
+
|
44
|
+
def attachment_url
|
45
|
+
return if attachments.nil?
|
46
|
+
return unless %w[image audio video file].include? attachment_type
|
47
|
+
|
48
|
+
attachments.first['payload']['url']
|
49
|
+
end
|
50
|
+
|
51
|
+
def location_coordinates
|
52
|
+
return [] unless attachment_type?('location')
|
53
|
+
|
54
|
+
coordinates_data = attachments.first['payload']['coordinates']
|
55
|
+
[coordinates_data['lat'], coordinates_data['long']]
|
56
|
+
end
|
57
|
+
|
58
|
+
def quick_reply
|
59
|
+
return unless @messaging['message']['quick_reply']
|
60
|
+
|
61
|
+
@messaging['message']['quick_reply']['payload']
|
62
|
+
end
|
63
|
+
alias_method :payload, :quick_reply
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def attachment_type?(attachment_type)
|
68
|
+
!attachments.nil? && attachments.first['type'] == attachment_type
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Bobot
|
2
|
+
module Event
|
3
|
+
class Postback
|
4
|
+
include Bobot::Event::Common
|
5
|
+
|
6
|
+
def payload
|
7
|
+
@messaging['postback']['payload']
|
8
|
+
end
|
9
|
+
|
10
|
+
def title
|
11
|
+
@messaging['postback']['title']
|
12
|
+
end
|
13
|
+
|
14
|
+
def referral
|
15
|
+
return if @messaging['postback']['referral'].nil?
|
16
|
+
@referral ||= Referral::Referral.new(@messaging['postback']['referral'])
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Bobot
|
2
|
+
module Event
|
3
|
+
class Referral
|
4
|
+
include Bobot::Event::Common
|
5
|
+
|
6
|
+
class Referral
|
7
|
+
def initialize(referral)
|
8
|
+
@referral = referral
|
9
|
+
end
|
10
|
+
|
11
|
+
def ref
|
12
|
+
@referral['ref']
|
13
|
+
end
|
14
|
+
|
15
|
+
def source
|
16
|
+
@referral['source']
|
17
|
+
end
|
18
|
+
|
19
|
+
def type
|
20
|
+
@referral['type']
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def referral
|
25
|
+
@referral ||= Referral.new(@messaging['referral'])
|
26
|
+
end
|
27
|
+
|
28
|
+
def ref
|
29
|
+
referral.ref
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Bobot
|
2
|
+
# Base Facebook Messenger exception.
|
3
|
+
class Error < ::StandardError; end
|
4
|
+
|
5
|
+
class InvalidParameter < ::ArgumentError
|
6
|
+
def initialize(name, description = "")
|
7
|
+
super("invalid value of parameter #{name}#{description.present? ? ": '#{description}'" : ''}")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class FieldFormat < ::ArgumentError
|
12
|
+
def initialize(description)
|
13
|
+
super("invalid field value with API limits: #{description}")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Forbidden Action exception
|
18
|
+
class ActionNotAllowed < Bobot::Error; end
|
19
|
+
|
20
|
+
# Base error class for Facebook API errors.
|
21
|
+
class FacebookError < Bobot::Error
|
22
|
+
attr_reader :message
|
23
|
+
attr_reader :type
|
24
|
+
attr_reader :code
|
25
|
+
attr_reader :subcode
|
26
|
+
attr_reader :user_title
|
27
|
+
attr_reader :user_msg
|
28
|
+
attr_reader :fbtrace_id
|
29
|
+
|
30
|
+
def initialize(error)
|
31
|
+
@message = error['message']
|
32
|
+
@type = error['type']
|
33
|
+
@code = error['code']
|
34
|
+
@subcode = error['error_subcode']
|
35
|
+
@user_title = error['error_user_title']
|
36
|
+
@user_msg = error['error_user_msg']
|
37
|
+
@fbtrace_id = error['fbtrace_id']
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_s
|
41
|
+
message
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Base Facebook Messenger send API exception.
|
46
|
+
class SendError < Bobot::FacebookError; end
|
47
|
+
|
48
|
+
class AccessTokenError < Bobot::SendError; end
|
49
|
+
class AccountLinkingError < Bobot::SendError; end
|
50
|
+
class BadParameterError < Bobot::SendError; end
|
51
|
+
class InternalError < Bobot::SendError; end
|
52
|
+
class LimitError < Bobot::SendError; end
|
53
|
+
class PermissionError < Bobot::SendError; end
|
54
|
+
|
55
|
+
# Base Facebook Messenger exception.
|
56
|
+
class NetworkError < ::StandardError
|
57
|
+
attr_reader :response
|
58
|
+
attr_reader :description
|
59
|
+
|
60
|
+
def initialize(response, description)
|
61
|
+
@response = response
|
62
|
+
@description = description
|
63
|
+
end
|
64
|
+
|
65
|
+
def message
|
66
|
+
description
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_s
|
70
|
+
message
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|