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.
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