bobot 1.0.52
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 +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
|