bobot 1.0.52

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +66 -0
  4. data/Rakefile +19 -0
  5. data/app/controllers/bobot/application_controller.rb +5 -0
  6. data/app/controllers/bobot/webhook_controller.rb +76 -0
  7. data/app/jobs/bobot/application_job.rb +4 -0
  8. data/app/jobs/bobot/commander_job.rb +9 -0
  9. data/app/jobs/bobot/deliver_job.rb +16 -0
  10. data/app/mailers/bobot/application_mailer.rb +6 -0
  11. data/app/models/bobot/application_record.rb +5 -0
  12. data/config/locales/bobot.en.yml +28 -0
  13. data/config/routes.rb +6 -0
  14. data/lib/bobot.rb +18 -0
  15. data/lib/bobot/buttons.rb +168 -0
  16. data/lib/bobot/commander.rb +68 -0
  17. data/lib/bobot/configuration.rb +206 -0
  18. data/lib/bobot/engine.rb +33 -0
  19. data/lib/bobot/error_parser.rb +102 -0
  20. data/lib/bobot/event.rb +40 -0
  21. data/lib/bobot/events/account_linking.rb +15 -0
  22. data/lib/bobot/events/common.rb +170 -0
  23. data/lib/bobot/events/delivery.rb +20 -0
  24. data/lib/bobot/events/message.rb +72 -0
  25. data/lib/bobot/events/message_echo.rb +8 -0
  26. data/lib/bobot/events/optin.rb +11 -0
  27. data/lib/bobot/events/postback.rb +20 -0
  28. data/lib/bobot/events/read.rb +15 -0
  29. data/lib/bobot/events/referral.rb +33 -0
  30. data/lib/bobot/exceptions.rb +73 -0
  31. data/lib/bobot/graph_facebook.rb +90 -0
  32. data/lib/bobot/profile.rb +23 -0
  33. data/lib/bobot/subscription.rb +19 -0
  34. data/lib/bobot/user.rb +13 -0
  35. data/lib/bobot/version.rb +14 -0
  36. data/lib/generators/bobot/install_generator.rb +28 -0
  37. data/lib/generators/bobot/templates/app/bobot/message.rb +3 -0
  38. data/lib/generators/bobot/templates/app/bobot/postback.rb +22 -0
  39. data/lib/generators/bobot/templates/app/bobot/workflow.rb +17 -0
  40. data/lib/generators/bobot/templates/config/bobot.yml +39 -0
  41. data/lib/generators/bobot/templates/config/initializers/bobot.rb +30 -0
  42. data/lib/generators/bobot/templates/config/locales/bobot.en.yml +30 -0
  43. data/lib/generators/bobot/templates/config/locales/bobot.fr.yml +29 -0
  44. data/lib/generators/bobot/uninstall_generator.rb +24 -0
  45. data/lib/generators/bobot/utils.rb +30 -0
  46. data/lib/tasks/bobot_tasks.rake +11 -0
  47. data/spec/bobot/bobot_spec.rb +24 -0
  48. data/spec/bobot/event/account_linking_spec.rb +59 -0
  49. data/spec/bobot/event/common_spec.rb +259 -0
  50. data/spec/bobot/event/delivery_spec.rb +62 -0
  51. data/spec/bobot/event/message_echo_spec.rb +276 -0
  52. data/spec/bobot/event/message_spec.rb +276 -0
  53. data/spec/bobot/event/optin_spec.rb +50 -0
  54. data/spec/bobot/event/postback_spec.rb +94 -0
  55. data/spec/bobot/event/read_spec.rb +51 -0
  56. data/spec/bobot/event/referral_spec.rb +66 -0
  57. data/spec/bobot/event_spec.rb +167 -0
  58. data/spec/bobot/install_generator_spec.rb +43 -0
  59. data/spec/bobot/profile_spec.rb +170 -0
  60. data/spec/bobot/subscription_spec.rb +139 -0
  61. data/spec/bobot/user_spec.rb +91 -0
  62. data/spec/controllers/bobot/application_controller_spec.rb +4 -0
  63. data/spec/controllers/bobot/webhook_controller_spec.rb +5 -0
  64. data/spec/dummy/Rakefile +6 -0
  65. data/spec/dummy/app/assets/config/manifest.js +3 -0
  66. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  67. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  68. data/spec/dummy/app/bobot/workflow.rb +17 -0
  69. data/spec/dummy/app/controllers/application_controller.rb +2 -0
  70. data/spec/dummy/app/jobs/application_job.rb +2 -0
  71. data/spec/dummy/app/models/application_record.rb +3 -0
  72. data/spec/dummy/bin/bundle +3 -0
  73. data/spec/dummy/bin/rails +4 -0
  74. data/spec/dummy/bin/rake +4 -0
  75. data/spec/dummy/bin/setup +35 -0
  76. data/spec/dummy/bin/update +29 -0
  77. data/spec/dummy/config.ru +5 -0
  78. data/spec/dummy/config/application.rb +30 -0
  79. data/spec/dummy/config/bobot.yml +27 -0
  80. data/spec/dummy/config/boot.rb +5 -0
  81. data/spec/dummy/config/database.yml +19 -0
  82. data/spec/dummy/config/environment.rb +5 -0
  83. data/spec/dummy/config/environments/development.rb +42 -0
  84. data/spec/dummy/config/environments/production.rb +78 -0
  85. data/spec/dummy/config/environments/test.rb +38 -0
  86. data/spec/dummy/config/initializers/application_controller_renderer.rb +6 -0
  87. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  88. data/spec/dummy/config/initializers/bobot.rb +30 -0
  89. data/spec/dummy/config/initializers/cors.rb +16 -0
  90. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  91. data/spec/dummy/config/initializers/inflections.rb +16 -0
  92. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  93. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  94. data/spec/dummy/config/locales/bobot.en.yml +28 -0
  95. data/spec/dummy/config/locales/bobot.fr.yml +27 -0
  96. data/spec/dummy/config/locales/en.yml +33 -0
  97. data/spec/dummy/config/puma.rb +56 -0
  98. data/spec/dummy/config/routes.rb +3 -0
  99. data/spec/dummy/config/secrets.yml +32 -0
  100. data/spec/dummy/config/spring.rb +6 -0
  101. data/spec/dummy/db/schema.rb +15 -0
  102. data/spec/examples.txt +111 -0
  103. data/spec/helpers/graph_api_helpers.rb +6 -0
  104. data/spec/jobs/bobot/commander_job_spec.rb +31 -0
  105. data/spec/lint/rubocop_spec.rb +8 -0
  106. data/spec/rails_helper.rb +67 -0
  107. data/spec/spec_helper.rb +105 -0
  108. data/spec/travis/database.travis.mysql.yml +19 -0
  109. 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,8 @@
1
+ module Bobot
2
+ module Event
3
+ # The Message echo class represents an
4
+ # incoming Facebook Messenger message
5
+ class MessageEcho < Message
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,11 @@
1
+ module Bobot
2
+ module Event
3
+ class Optin
4
+ include Bobot::Event::Common
5
+
6
+ def ref
7
+ @messaging['optin']['ref']
8
+ end
9
+ end
10
+ end
11
+ 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,15 @@
1
+ module Bobot
2
+ module Event
3
+ class Read
4
+ include Bobot::Event::Common
5
+
6
+ def at
7
+ Time.zone.at(@messaging['read']['watermark'] / 1000)
8
+ end
9
+
10
+ def seq
11
+ @messaging['read']['seq']
12
+ end
13
+ end
14
+ end
15
+ 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