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,90 @@
1
+ module Bobot
2
+ module GraphFacebook
3
+ GRAPH_FB_URL = 'https://graph.facebook.com'.freeze
4
+ GRAPH_FB_VERSION = 'v2.10'.freeze
5
+
6
+ module ClassMethods
7
+ def graph_get(path, query: {})
8
+ uri = URI.parse(File.join(GRAPH_FB_URL, GRAPH_FB_VERSION, path))
9
+ uri.query = URI.encode_www_form(query)
10
+ req = Net::HTTP::Get.new(uri.request_uri)
11
+ req['Content-Type'] = 'application/json; charset=utf-8'
12
+ req['Accept'] = 'application/json'
13
+ req['User-Agent'] = nil
14
+ req['Accept-Encoding'] = nil
15
+ https = Net::HTTP.new(uri.host, uri.port).tap do |http|
16
+ http.use_ssl = true
17
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
18
+ http.read_timeout = 300
19
+ end
20
+ res = https.request(req)
21
+ json = ActiveSupport::JSON.decode(res.send(:body) || '{}')
22
+ if Bobot.debug_log
23
+ puts "[GET] >> #{uri.request_uri}"
24
+ puts "[GET] << #{json}"
25
+ end
26
+ Bobot::ErrorParser.raise_errors_from(json)
27
+ json
28
+ end
29
+ module_function :graph_get
30
+
31
+ def graph_post(path, query: {}, body: {})
32
+ uri = URI.parse(File.join(GRAPH_FB_URL, GRAPH_FB_VERSION, path))
33
+ uri.query = URI.encode_www_form(query)
34
+ req = Net::HTTP::Post.new(uri.request_uri)
35
+ req['Content-Type'] = 'application/json; charset=utf-8'
36
+ req['Accept'] = 'application/json'
37
+ req['User-Agent'] = nil
38
+ req['Accept-Encoding'] = nil
39
+ req.body = ActiveSupport::JSON.encode(body)
40
+ https = Net::HTTP.new(uri.host, uri.port).tap do |http|
41
+ http.use_ssl = true
42
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
43
+ http.read_timeout = 300
44
+ end
45
+ res = https.request(req)
46
+ json = ActiveSupport::JSON.decode(res.send(:body) || '{}')
47
+ if Bobot.debug_log
48
+ puts "[POST] >> #{uri.request_uri}"
49
+ puts "[POST] << #{json}"
50
+ end
51
+ Bobot::ErrorParser.raise_errors_from(json)
52
+ json
53
+ end
54
+ module_function :graph_post
55
+
56
+ def graph_delete(path, query: {}, body: {})
57
+ uri = URI.parse(File.join(GRAPH_FB_URL, GRAPH_FB_VERSION, path))
58
+ uri.query = URI.encode_www_form(query)
59
+ req = Net::HTTP::Delete.new(uri.request_uri)
60
+ req['Content-Type'] = 'application/json; charset=utf-8'
61
+ req['Accept'] = 'application/json'
62
+ req['User-Agent'] = nil
63
+ req['Accept-Encoding'] = nil
64
+ req.body = ActiveSupport::JSON.encode(body)
65
+ https = Net::HTTP.new(uri.host, uri.port).tap do |http|
66
+ http.use_ssl = true
67
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
68
+ http.read_timeout = 300
69
+ end
70
+ res = https.request(req)
71
+ json = ActiveSupport::JSON.decode(res.send(:body) || '{}')
72
+ if Bobot.debug_log
73
+ puts "[DELETE] >> #{uri.request_uri}"
74
+ puts "[DELETE] << #{json}"
75
+ end
76
+ Bobot::ErrorParser.raise_errors_from(json)
77
+ json
78
+ end
79
+ module_function :graph_delete
80
+ end
81
+
82
+ module InstanceMethods
83
+ end
84
+
85
+ def self.included(receiver)
86
+ receiver.extend ClassMethods
87
+ receiver.send :include, InstanceMethods
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,23 @@
1
+ module Bobot
2
+ module Profile
3
+ include Bobot::GraphFacebook
4
+
5
+ def set(body:, query: nil)
6
+ query ||= { access_token: Bobot.page_access_token }
7
+ query[:access_token] = Bobot.page_access_token unless query.key?("access_token")
8
+ graph_post '/me/messenger_profile', body: body, query: {
9
+ access_token: query.fetch(:access_token),
10
+ }
11
+ end
12
+ module_function :set
13
+
14
+ def unset(body:, query: nil)
15
+ query ||= { access_token: Bobot.page_access_token }
16
+ query[:access_token] = Bobot.page_access_token unless query.key?("access_token")
17
+ graph_delete '/me/messenger_profile', body: body, query: {
18
+ access_token: query.fetch(:access_token),
19
+ }
20
+ end
21
+ module_function :unset
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ module Bobot
2
+ module Subscription
3
+ include Bobot::GraphFacebook
4
+
5
+ def set(query: {})
6
+ graph_post "/#{query.fetch(:page_id)}/subscribed_apps", query: {
7
+ access_token: query.fetch(:access_token),
8
+ }
9
+ end
10
+ module_function :set
11
+
12
+ def unset(query: {})
13
+ graph_delete "/#{query.fetch(:page_id)}/subscribed_apps", query: {
14
+ access_token: query.fetch(:access_token),
15
+ }
16
+ end
17
+ module_function :unset
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ module Bobot
2
+ module User
3
+ include Bobot::GraphFacebook
4
+
5
+ def get_profile(query:)
6
+ graph_get "/#{query.fetch(:fb_id)}", query: {
7
+ fields: query.fetch(:fields).join(','),
8
+ access_token: query.fetch(:access_token),
9
+ }
10
+ end
11
+ module_function :get_profile
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ module Bobot
2
+ class Version
3
+ MAJOR = 1
4
+ MINOR = 0
5
+ PATCH = 52
6
+ PRE = nil
7
+
8
+ class << self
9
+ def to_s
10
+ [MAJOR, MINOR, PATCH, PRE].compact.join('.')
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,28 @@
1
+ require 'pathname'
2
+ require 'rails/generators'
3
+ require File.expand_path('../utils', __FILE__)
4
+
5
+ module Bobot
6
+ class InstallGenerator < Rails::Generators::Base
7
+ include Rails::Generators::Migration
8
+ include Generators::Utils::InstanceMethods
9
+ extend Generators::Utils::ClassMethods
10
+ source_root File.expand_path('../templates', __FILE__)
11
+
12
+ argument :_namespace, type: :string, required: false, desc: 'Bot url namespace'
13
+ desc 'Bobot install'
14
+
15
+ def install
16
+ namespace = ask_for('Where do you want to mount bobot?', 'bot', _namespace)
17
+ create_file 'config/routes.rb'
18
+ gsub_file 'config/routes.rb', %r{mount Bobot::Engine => \'\/.+\'(, as: \'bot\')?}, ''
19
+ route("mount Bobot::Engine => '/#{namespace}', as: 'bot'")
20
+ template 'config/initializers/bobot.rb', 'config/initializers/bobot.rb'
21
+ template 'app/bobot/workflow.rb', 'app/bobot/workflow.rb'
22
+ copy_file 'config/bobot.yml', 'config/bobot.yml'
23
+ copy_file 'config/locales/bobot.en.yml', 'config/locales/bobot.en.yml'
24
+ copy_file 'config/locales/bobot.fr.yml', 'config/locales/bobot.fr.yml'
25
+ display 'Installation done', :green
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,3 @@
1
+ class Message
2
+ def perform(message); end
3
+ end
@@ -0,0 +1,22 @@
1
+ module Bobot
2
+ class Postback
3
+ def self.perform(event)
4
+ payload = event.payload
5
+ begin
6
+ payload = ActiveSupport::JSON.decode(payload)
7
+ step = payload["step"]
8
+ params = payload["params"].hash
9
+ rescue ::ActiveSupport::JSON.parse_error
10
+ step = payload.to_sym
11
+ params = nil
12
+ end
13
+
14
+ bot = Postback.new(event)
15
+ if bot.respond_to?(step)
16
+ bot.public_send(*([step, params].compact))
17
+ else
18
+ bot.unknown_payload
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ Bobot::Commander.on :message do |message|
2
+ klass = message.quick_reply.present? ? Bobot::Postback : Bobot::Message
3
+ klass.perform(message)
4
+ end
5
+
6
+ Bobot::Commander.on :postback do |postback|
7
+ klass = Bobot::Postback
8
+ klass.perform(postback)
9
+ end
10
+
11
+ # Bobot::Commander.on :optin do |optin|
12
+ # optin.reply_with_text(text: 'Ah, human! Clicked on Send To Messenger')
13
+ # end
14
+ #
15
+ # Bobot::Commander.on :referral do |referral|
16
+ # optin.reply_with_text(text: "Great you came from #{referal.ref}")
17
+ # end
@@ -0,0 +1,39 @@
1
+ development:
2
+ app_id: "123"
3
+ app_secret: "456"
4
+ page_id: "789"
5
+ page_access_token: "abc"
6
+ verify_token: "your token"
7
+ domains: "whitelisted-domain.com,second-whitelisted-domain.com"
8
+ debug_log: true
9
+ async: false
10
+
11
+ test:
12
+ app_id: "123"
13
+ app_secret: "456"
14
+ page_id: "789"
15
+ page_access_token: "abc"
16
+ verify_token: "your token"
17
+ domains: "whitelisted-domain.com,second-whitelisted-domain.com"
18
+ debug_log: true
19
+ async: false
20
+
21
+ staging:
22
+ app_id: <%= ENV["STAGING_BOBOT_APP_ID"] %>
23
+ app_secret: <%= ENV["STAGING_BOBOT_APP_SECRET"] %>
24
+ page_id: <%= ENV["STAGING_BOBOT_PAGE_ID"] %>
25
+ page_access_token: <%= ENV["STAGING_BOBOT_PAGE_ACCESS_TOKEN"] %>
26
+ verify_token: <%= ENV["STAGING_BOBOT_VERIFY_TOKEN"] %>
27
+ domains: <%= ENV["STAGING_BOBOT_DOMAINS"] %>
28
+ debug_log: <%= ENV["STAGING_BOBOT_DEBUG_LOG"] %>
29
+ async: <%= ENV["STAGING_BOBOT_ASYNC"] %>
30
+
31
+ production:
32
+ app_id: <%= ENV["PRODUCTION_BOBOT_APP_ID"] %>
33
+ app_secret: <%= ENV["PRODUCTION_BOBOT_APP_SECRET"] %>
34
+ page_id: <%= ENV["PRODUCTION_BOBOT_PAGE_ID"] %>
35
+ page_access_token: <%= ENV["PRODUCTION_BOBOT_PAGE_ACCESS_TOKEN"] %>
36
+ verify_token: <%= ENV["PRODUCTION_BOBOT_VERIFY_TOKEN"] %>
37
+ domains: <%= ENV["PRODUCTION_BOBOT_DOMAINS"] %>
38
+ debug_log: <%= ENV["PRODUCTION_BOBOT_DEBUG_LOG"] %>
39
+ async: <%= ENV["PRODUCTION_BOBOT_ASYNC"] %>
@@ -0,0 +1,30 @@
1
+ bobot_config_path = Rails.root.join("config", "bobot.yml")
2
+ bobot_config = YAML.safe_load(ERB.new(File.read(bobot_config_path)).result)[Rails.env]
3
+
4
+ if bobot_config.present?
5
+ Bobot.configure do |config|
6
+ config.app_id = bobot_config["app_id"]
7
+ config.app_secret = bobot_config["app_secret"]
8
+ config.page_access_token = bobot_config["page_access_token"]
9
+ config.page_id = bobot_config["page_id"]
10
+ config.verify_token = bobot_config["verify_token"]
11
+ config.domains = bobot_config["domains"]
12
+ config.debug_log = bobot_config["debug_log"]
13
+ config.async = bobot_config["async"]
14
+ end
15
+ else
16
+ warn "#{bobot_config_path} not configured yet in #{Rails.env} environment."
17
+ end
18
+
19
+ unless Rails.env.production?
20
+ bot_files = Dir[Rails.root.join("app", "bobot", "**", "*.rb")]
21
+ bot_reloader = ActiveSupport::FileUpdateChecker.new(bot_files) do
22
+ bot_files.each { |file| require_dependency file }
23
+ end
24
+
25
+ ActiveSupport::Reloader.to_prepare do
26
+ bot_reloader.execute_if_updated
27
+ end
28
+
29
+ bot_files.each { |file| require_dependency file }
30
+ end
@@ -0,0 +1,30 @@
1
+ en:
2
+ bobot:
3
+ config:
4
+ facebook_locales:
5
+ - "en_US"
6
+ - "en_UD"
7
+ - "en_GB"
8
+ greeting_text: "Bobot is an intelligent robot."
9
+ get_started:
10
+ payload: 'get_started'
11
+ persistent_menu:
12
+ composer_input_disabled: false
13
+ call_to_actions:
14
+ - title: "My Account"
15
+ type: "nested"
16
+ call_to_actions:
17
+ - title: "What is a chatbot?"
18
+ type: "postback"
19
+ payload: "WHAT_IS_A_CHATBOT"
20
+ - title: "History"
21
+ type: "postback"
22
+ payload: "HISTORY_PAYLOAD"
23
+ - title: "Contact Info"
24
+ type: "postback"
25
+ payload: "CONTACT_INFO_PAYLOAD"
26
+ - type: "web_url"
27
+ title: "Get some help"
28
+ url: "https://github.com/navidemad/bobot"
29
+ webview_height_ratio: "full"
30
+ what_is_a_chatbot: "A chatbot is a computer program which conducts a conversation via auditory or textual methods."
@@ -0,0 +1,29 @@
1
+ fr:
2
+ bobot:
3
+ config:
4
+ facebook_locales:
5
+ - "fr_FR"
6
+ - "fr_CA"
7
+ greeting_text: 'Bobot est un robot intelligent.'
8
+ get_started:
9
+ payload: 'get_started'
10
+ persistent_menu:
11
+ composer_input_disabled: false
12
+ call_to_actions:
13
+ - title: "Mon compte"
14
+ type: "nested"
15
+ call_to_actions:
16
+ - title: "C'est quoi un chatbot ?"
17
+ type: "postback"
18
+ payload: "WHAT_IS_A_CHATBOT"
19
+ - title: "Historique"
20
+ type: "postback"
21
+ payload: "HISTORY_PAYLOAD"
22
+ - title: "Contact Info"
23
+ type: "postback"
24
+ payload: "CONTACT_INFO_PAYLOAD"
25
+ - type: "web_url"
26
+ title: "Obtenir de l'aide"
27
+ url: "https://github.com/navidemad/bobot"
28
+ webview_height_ratio: "full"
29
+ what_is_a_chatbot: "Un chatbot est un programme qui tente de converser avec une personne durant quelques minutes ou plus en lui donnant l'impression de converser elle-même avec une personne."
@@ -0,0 +1,24 @@
1
+ require 'rails/generators'
2
+ require File.expand_path('../utils', __FILE__)
3
+
4
+ module Bobot
5
+ class UninstallGenerator < Rails::Generators::Base
6
+ include Rails::Generators::Migration
7
+ include Generators::Utils::InstanceMethods
8
+ extend Generators::Utils::ClassMethods
9
+ source_root File.expand_path('../templates', __FILE__)
10
+
11
+ desc 'Bobot uninstall'
12
+
13
+ def uninstall
14
+ display 'Why you leaving so soon? :('
15
+ gsub_file 'config/routes.rb', %r{mount Bobot::Engine => \'\/.+\'(, as: \'bot\')?}, ''
16
+ remove_file 'config/initializers/bobot.rb'
17
+ remove_file 'app/bobot/workflow.rb'
18
+ remove_file 'config/bobot.yml'
19
+ remove_file 'config/locales/bobot.en.yml'
20
+ remove_file 'config/locales/bobot.fr.yml'
21
+ display 'Done! Bobot has been uninstalled.'
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,30 @@
1
+ module Bobot
2
+ module Generators
3
+ module Utils
4
+ module InstanceMethods
5
+ def display(output, color = :green)
6
+ say(" - #{output}", color)
7
+ end
8
+
9
+ def ask_for(wording, default_value = nil, override_if_present_value = nil)
10
+ if override_if_present_value.present?
11
+ display("Using [#{override_if_present_value}] for question '#{wording}'") && override_if_present_value
12
+ else
13
+ ask(" ? #{wording} Press <enter> for [#{default_value}] >", :yellow).presence || default_value
14
+ end
15
+ end
16
+ end
17
+ module ClassMethods
18
+ def next_migration_number(dirname)
19
+ if ActiveRecord::Base.timestamped_migrations
20
+ migration_number = Time.current.strftime("%Y%m%d%H%M%S").to_i
21
+ migration_number += 1
22
+ migration_number.to_s
23
+ else
24
+ format("%.3d", current_migration_number(dirname) + 1)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,11 @@
1
+ namespace :bobot do
2
+ desc 'Install bobot'
3
+ task :install do
4
+ system 'rails g bobot:install'
5
+ end
6
+
7
+ desc 'Uninstall bobot'
8
+ task :uninstall do
9
+ system 'rails g bobot:uninstall'
10
+ end
11
+ end