rubotnik 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 722a334f07d4832bd7b5c997ec9a466d167a1793
4
+ data.tar.gz: e05b518c7dffcbcf98e539f36491322aba63d827
5
+ SHA512:
6
+ metadata.gz: bd63042e7060157641084648f576ec3d058dc5b0d911575aae11ba0efc960d48635b7e630dfb3ade7cad2d72ad602fa47668d44b9ef4dff9ccf15dc1739001dc
7
+ data.tar.gz: 67ddf59877b68e3bffe51c7b5f51e69a33af8d7f2362b4e5cbf97449e3c518e609cf8937900be0015965fa500475a862f0245df14b54e0197c290254ad278973
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.3
5
+ before_install: gem install bundler -v 1.14.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rubotnik.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Andy Barnov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,414 @@
1
+ > :warning: Hooray! Rubotnik is now a gem that can generate a project for you and serve it from localhost or Heroku. There's been some API changes since it has been a boilerplate. If you are looking for an old README and rubotnik-boilerplate's code you can find it [here](https://github.com/progapandist/rubotnik/tree/boilerplate-legacy).
2
+
3
+ # Rubotnik
4
+
5
+ Tiny "bot-end" Ruby framework with intuitive DSL to quickly build, test and deploy a Messenger chatbot 🤖.
6
+
7
+ Only minimal Ruby knowledge is required. Perfect for pet projects, classrooms and building bot MVPs from scratch while having a complete freedom over what your bot can do ([ChatFuel](https://chatfuel.com/) is great, but what if you actually _want_ to code?). Probably not the best solution for commercial projects at the moment—Rubotnik is yet to prove its worth in production.
8
+
9
+ This is how you greet your users with Rubotnik:
10
+
11
+ ```ruby
12
+ Rubotnik.route :message do
13
+ bind 'hi', 'hello', 'bonjour' do
14
+ say 'Hello from your new bot!'
15
+ end
16
+ end
17
+ ```
18
+
19
+ Rubotnik is zero-configuration, you can have a conversation with your bot on Messenger in __under 10 minutes__ (and most of that time you'll spend on Facebook and Facebook for Developers, creating a page and an "app" for the bot).
20
+
21
+ Rubotnik is built on top of an excellent [facebook-messenger gem](https://github.com/jgorset/facebook-messenger) by [Johannes Gorset](https://github.com/jgorset) that does all the heavy-lifting to actually interact with the Messenger Platform API. While `facebook-messenger` implements a client, `rubotnik` offers you a way to reason about your bot design without dozens of nested `if` and `case` statements. Rubotnik comes with a bare-bones architecture that ensures multiple connected users are served without delays or overlaps.
22
+
23
+ ## What the heck is "bot-end"?
24
+
25
+ Exactly as with modern front-end, "bot-end" is a separate web application that can talk with your back-end through a (REST) API. Project generated with Rubotnik uses Puma as web server, contains its own `config.ru` and can be deployed to Heroku in a matter of minutes. And you can still use Heroku's free account, as "sleep time" is not a deciding factor in bot interactions.
26
+
27
+ Rubotnik currently __can not__ be integrated directly inside the Rails project (although thoughts on that are welcome) and does not have a database adapter of its own __on purpose__, to keep the boilerplate to the very minimum and extract conversational logic to a separate layer.
28
+
29
+ In other words:
30
+
31
+ > Rubotnik is perfect to consume existing APIs from a chatbot interface
32
+
33
+ __NB__: Rubotnik comes with [httparty](https://github.com/jnunemaker/httparty) library to make HTTP requests, but you can use any other tool by including it in the project's Gemfile.
34
+
35
+ ## Installation
36
+
37
+ It is recommended to install [Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli), [Ngrok](https://ngrok.com/) and [Bundler](http://bundler.io/) before you start.
38
+
39
+ ```bash
40
+ $ gem install rubotnik
41
+ ```
42
+
43
+ And then create your bot with:
44
+
45
+ ```bash
46
+ $ rubotnik new PROJECT_NAME
47
+ ```
48
+
49
+ Now you can `cd` to `PROJECT_NAME`, open it in your favorite editing and start hacking!
50
+
51
+ To start playing with your bot directly from Messenger (after you've [created](https://github.com/progapandist/rubotnik#setup) an app in Facebook Developer Console):
52
+
53
+ ```bash
54
+ $ bundle install
55
+ $ heroku local
56
+ ```
57
+
58
+ And in a new Terminal tab:
59
+
60
+ ```bash
61
+ $ ngrok http 5000
62
+ ```
63
+
64
+ ## Usage
65
+
66
+ A project generated with `rubotnik new` will have a following simple structure:
67
+
68
+ ```
69
+ .
70
+ ├── Gemfile # contains a single dependency on "rubotnik" gem
71
+ ├── Procfile # ready for Heroku
72
+ ├── bot
73
+ │   ├── bot.rb # <-- YOUR STARTING POINT
74
+ │   ├── commands
75
+ │   │   ├── commands.rb # define any commands as methods here
76
+ │   │   ├── location.rb # or here (contains example for handling user location)
77
+ │   │   └── ui_examples.rb # or here (contains examples for UI elements)
78
+ │   └── profile.rb # Welcome page, "Get Started" button and a menu for your bot
79
+ ├── config
80
+ │   └── puma.rb # Puma settings
81
+ └── config.ru # it's a Rack app, after all!
82
+ ```
83
+
84
+ All the magic happens inside `Rubotnik.route :message` and `Rubotnik.route :postback` blocks, this is where you can use Rubotnik's DSL to bind user messages to "commands" that your bot is going to execute.
85
+
86
+ A "command" is just a plain Ruby _method_ inside the `Commands` module that has access to `message` (or `postback`) and `user` objects. This is how you match incoming message to a command:
87
+
88
+ ```ruby
89
+
90
+ # Will match any of the words and their variations
91
+ bind "hello", "hi", "bonjour", "привет" to: :my_method_for_greeting
92
+
93
+ # Will match only if all words are present (ignoring order)
94
+ bind "what", "time", "is", all: true, to: :tell_time
95
+
96
+ # Same logic for postbacks
97
+ bind "ACTION_BUTTON", to: :action_for_button
98
+
99
+ ```
100
+
101
+ `bind` can also take a block for a simple response that does not merit its own method:
102
+
103
+ ```ruby
104
+ bind "damn" do
105
+ say "Watch your language, human!"
106
+ end
107
+ ```
108
+
109
+ If none of the commands are matched, a `default` block will be invoked
110
+
111
+ ```ruby
112
+ # Invoked if none of the commands recognized. Has to come last, after all binds
113
+ default do
114
+ say "Come again?"
115
+ end
116
+ ```
117
+
118
+ ## Setup
119
+
120
+ * Login to [Facebook For Developers](https://developers.facebook.com/). In the top right corner, select _My apps > Add a new app_.
121
+
122
+ * Select a product _"Messenger"_
123
+
124
+ * Under _Token Generation_ in _Products > Messenger > Settings_ select a page for your bot, or create a new one. Copy the __Page Access Token__ and insert it in `.env` in the Rubotnik-generated project under `ACESS_TOKEN`
125
+
126
+ * While still in `.env`, come up with any string for `VERIFY_TOKEN` variable (or leave the default `verify_me`)
127
+
128
+ * From the Terminal, while being in the same folder as your project, open a new tab and run `heroku local`. It will load environment variables from `.env` and start Puma server on port 5000.
129
+
130
+ * Open another tab und run `ngrok http 5000` that will expose port 5000 to the Internet. (note, depending on your Ngrok installation, you may want to specify path to `ngrok` executable when running it).
131
+
132
+ * Copy the _Forwarding_ address from the ngrok tab. It should start with __https://__
133
+
134
+ * In the Facebook dashboard, while still under _Products > Messenger > Settings_, click _Setup Webhooks_. Under _Subscription Fields_ select _"messages"_ and _"messaging_postbacks"_. Under _Callback URL_, paste your ngrok secure forwarding address and postpend it with `/webhook` (that's important!). Put your verification token under _Verify Token_
135
+
136
+ * Click _Verify and Save_. Once the modal closes, you will see that under _Webhooks_ section in the dashboard you can now _"Select a page to subscribe your webhook to the page events"_. Select your page and hit _"Subscribe"_.
137
+
138
+ * You're done! Now you can find your bot in Messenger under your page's name and start talking to it. Check with your generated project to see what commands are included for demonstration purposes. Start tweaking the bot to your liking!
139
+
140
+
141
+ ## Debugging your bot
142
+
143
+ Debugging bots under Facebook Messenger Platorfm is not the most pleasant experience — if your client can not handle the `POST` request to your webhook from the API that contains a message relayed from user — Facebook will try resending the message again and again, before finally disabling your bot if it still can not return a 200 response. In order to save you from that trouble, Rubotnik sets `$DEBUG` environment variable to "true" while you're on localhost.
144
+
145
+ While in this mode, every `StandardError` exception raised by your bot's code will be forwarded as a bot response to the chat dialogue, ensuring the conversation flow is never broken. As a developer, you will be able to see what is wrong with your bot _while talking it_. I find it a very natural experience.
146
+
147
+ Note that Rubotnik does not reload the server on code change. Every time you want to add a new feature or modify an existing one—you will have to relaunch `heroku local` after code save.
148
+
149
+ Currently, Rubotnik has quite a verbose logging to `stdout` that will help you make sense of what's going on.
150
+
151
+ # Developing with Rubotnik
152
+
153
+ ## Messages, postbacks and menus
154
+
155
+ Your starting point is `bot.rb` file that serves your bot, enables its persistent menu and a greeting screen, and provides top-level routing for messages and postbacks.
156
+
157
+ Received messages and postbacks are instances of `Facebook::Messenger::Incoming::Message` and `Facebook::Messenger::Incoming::Postback` objects of [facebook-messenger](https://github.com/hyperoslo/facebook-messenger) gem and have all the properties defined in its README.
158
+
159
+ ```ruby
160
+
161
+ message.id # => 'mid.1457764197618:41d102a3e1ae206a38'
162
+ message.sender # => { 'id' => '1008372609250235' }
163
+ message.seq # => 73
164
+ message.sent_at # => 2016-04-22 21:30:36 +0200
165
+ message.text # => 'Hello, bot!'
166
+ message.attachments # => [ { 'type' => 'image', 'payload' => { 'url' => 'https://www.example.com/1.jpg' } } ]
167
+
168
+ ```
169
+
170
+ `profile.rb` contains constants that provide hash structures (mimicking JSON from Facebook docs) for payloads necessary to enable the "Start Button", "Welcome Screen" and "Persistent Menu" of your bot. Modify them to your liking. Note that it's better not to change them very often after enabling them, as Facebook caches interface elements.
171
+
172
+ You can enable them all or some of them by putting `# Rubotnik.set_profile(Profile::START_BUTTON, Profile::START_GREETING, Profile::SIMPLE_MENU)` in your code.
173
+
174
+ ## Users
175
+
176
+ Every connected user will be given it's own `User` object that is stored in the hash in memory, while your bot is running. You can reference `user` or `@user` (both will work) from anywhere inside your code.
177
+
178
+ `user.id` will give you an unique identifier for connected user (assigned by Facebook), and `user.session` is a hash that can be used for storing any user-related data during the session. For instance, you can design a thread of conversation that will ask user for some information, keep bits of that information as different keys under `user.session` hash and make a POST call with httparty (included in your project) or any other tool to your application back-end to persist the data.
179
+
180
+ ## Conversation threads
181
+
182
+ One of the most painful points in bot development is conversation branching. Rubotnik tries to make that a little bit more intuitive by allowing you to chain "command" methods together. Here's an example that you will find in the generated project.
183
+
184
+ In `bot.rb`:
185
+
186
+ ```ruby
187
+ Rubotnik.route :message do
188
+ bind 'how', 'do', 'you', all: true, to: :start_conversation, reply_with: {
189
+ text: "I'm doing fine! You?",
190
+ quick_replies: [['Good!', 'OK'], ['Not so well', 'NOT_OK']]
191
+ }
192
+ end
193
+ ```
194
+
195
+ Any message from user that has any combination of words "how", "do" and "you" (e.g. "How are you doing?", "How do you do?") will trigger a response defined under `reply_with`. `text` is a text of the response and `quick_replies` is an optional array of quick replies (maximum 11) that will be attached to your bot's message like this:
196
+
197
+ ![quick replies](./docs/quick_replies.PNG)
198
+
199
+ Any further user action will now be handled by `start_conversation` command under Commands module. If the user chooses to ignore "quick replies" hints and just types something in the message box, that will be a regular `message.text`, if he clicks on one of supplied quick replies, that will result in a received `message` with two properties: `message.text # => "Good!"` and `message.quick_reply # => "OK"`. Now you can handle it in `start_conversation`:
200
+
201
+ ```ruby
202
+ def start_conversation
203
+ message.typing_on # simulate "typing"
204
+ case message.quick_reply # switch on quick_reply property
205
+ when 'OK'
206
+ say "Glad you're doing well!"
207
+ # end conversation, further input will be handled by top level bindings
208
+ # defined under Rubotnik.route :message
209
+ stop_thread
210
+ when 'NOT_OK'
211
+ say "Too bad. What happened?"
212
+ next_command :appear_nice # pass the control further down the thread
213
+ else
214
+ say "🤖"
215
+ # it's always a good idea to have an else, quick replies don't
216
+ # prevent user from typing any message in the dialogue
217
+ stop_thread
218
+ end
219
+ message.typing_off
220
+ end
221
+
222
+ def appear_nice
223
+ message.typing_on
224
+ case message.text
225
+ when /job/i then say "We've all been there"
226
+ when /family/i then say "That's just life"
227
+ else
228
+ say "It shall pass"
229
+ end
230
+ message.typing_off
231
+ stop_thread # end conversation
232
+ end
233
+ ```
234
+
235
+ This is the breakdown of the sequence of events:
236
+
237
+ * User sends _"How do you do"_ =>
238
+ * `Rubotnik.route :message` binding to `start_conversation` command is invoked with a `reply_with` option =>
239
+ * Your bot replies with "I'm doing fine!" and attaches quick replies "Good!" and "Not so well" to the message =>
240
+ * User selects "Good!" =>
241
+ * A user message with `message.text` "Good!" and `message.quick_reply` "OK" is handled by `start_conversation` method. You can switch on any or both properties =>
242
+ * Method either passes control to a next method with `next_command` or stops interaction (returning user to the "top level" of conversation) with `stop_thread`
243
+
244
+
245
+ ## Helpers
246
+
247
+ Rubotnik gives you a number of helper methods (defined [here](https://github.com/progapandist/rubotnik/blob/master/lib/rubotnik/helpers.rb):
248
+
249
+ * `say`: `say "Hi"` or `say "Choose a pill", quick_replies: %w[Red Blue]` will immediately send a message to the user. `quick_replies` accepts either an array of strings (then `payload` option is automatically set to text in ALL CAPS), an array of string arrays (`['Reply Text', 'REPLY_PAYLOAD']`), or an array of hashes that follow Facebook's [format](https://developers.facebook.com/docs/messenger-platform/send-messages/quick-replies):
250
+
251
+ ```ruby
252
+ [
253
+ {
254
+ content_type: "text",
255
+ title: "<BUTTON_TEXT>",
256
+ image_url: "http://example.com/img/red.png",
257
+ payload: "<STRING_SENT_TO_WEBHOOK>"
258
+ },
259
+ {...}
260
+ ]
261
+ ```
262
+
263
+ * `show` takes an instance of `UI` element and sends it to the connected user.
264
+
265
+ * `text_message?` allows you to check if the message received from the user contains text (and isn't a GIF, a sticker or anything else). Useful for implementing sanity checks.
266
+
267
+ * `message_contains_location?` checks if the user has shared a location with your bot. Then you can access its coordinates with `message.attachments`.
268
+
269
+ * `get_user_info(*fields)` takes a list of fields good for [Graph API User](https://developers.facebook.com/docs/graph-api/reference/v2.2/user) and makes a call to the Graph referencing connected user's id and requesting specified fields. Returns a hash with user data. Keys are symbols.
270
+
271
+ ```ruby
272
+ get_user_info(:first_name, :last_name) # => { first_name: "John", last_name: "Doe" }
273
+ ```
274
+
275
+ * `next_command :command_name` and `stop_thread` are used to control conversation flow.
276
+
277
+ ## Supported UI elements
278
+
279
+ ### Button Template
280
+
281
+ `UI::FBButtonTemplate` takes two arguments: string for the text message and an array of hashes for buttons. See [types of buttons available](https://developers.facebook.com/docs/messenger-platform/send-api-reference/buttons) in Messenger Platform docs.
282
+
283
+ ```ruby
284
+ TEXT = "Look, I'm a message and I have some buttons attached!"
285
+ BUTTONS = [
286
+ {
287
+ type: :web_url,
288
+ url: 'https://medium.com/@progapanda',
289
+ title: "Andy's Medium"
290
+ },
291
+ {
292
+ type: :postback,
293
+ payload: 'BUTTON_TEMPLATE_ACTION',
294
+ title: 'Useful Button'
295
+ }
296
+ ]
297
+
298
+ # create template object and send it to connected user
299
+ template = UI::FBButtonTemplate.new(TEXT, BUTTONS)
300
+ show(template)
301
+ ```
302
+
303
+ If you have a button of type 'postback', you will be responsible to implementing the trigger for that action under `Rubotnik.route :postback` as `bind 'BUTTON_TEMPLATE_ACTION', to: :do_smth_on_button_click`.
304
+
305
+ ![button template](./docs/button_template.png)
306
+
307
+ ### Generic Template
308
+
309
+ [Generic template](https://developers.facebook.com/docs/messenger-platform/send-api-reference/generic-template) is a way to send the user a carousel of items, each consisting of an image, a title, a description and up to 3 action buttons. Each card can be made clickable and link to a website. Constructing Generic Template involves building a long nested JSON (refer to Facebook docs to see what keys are available) and Rubotnik tries to abstract it at least a little bit. You only need to build hashes for the `elements` array of the original documentation. Create your structure and save it in a constant:
310
+
311
+ ```ruby
312
+ # A carousel with two items (platform supports up to 10)
313
+ CAROUSEL = [
314
+ {
315
+ title: 'Random image',
316
+ # Horizontal image should have 1.91:1 ratio
317
+ image_url: 'https://unsplash.it/760/400?random',
318
+ subtitle: "That's a first card in a carousel",
319
+ default_action: {
320
+ type: 'web_url',
321
+ url: 'https://unsplash.it'
322
+ },
323
+ buttons: [
324
+ {
325
+ type: :web_url,
326
+ url: 'https://unsplash.it',
327
+ title: 'Website'
328
+ }
329
+ ]
330
+ },
331
+ {
332
+ title: 'Another random image',
333
+ # Horizontal image should have 1.91:1 ratio
334
+ image_url: 'https://unsplash.it/600/315?random',
335
+ subtitle: "And here's a second card. You can add up to 10!",
336
+ default_action: {
337
+ type: 'web_url',
338
+ url: 'https://unsplash.it'
339
+ },
340
+ buttons: [
341
+ {
342
+ type: :web_url,
343
+ url: 'https://unsplash.it',
344
+ title: 'Website'
345
+ }
346
+ ]
347
+ }
348
+ ]
349
+ ```
350
+
351
+ Then you can create an instance of `UI::FBCarousel` by passing your template to the constructor. Then you can send it to the user.
352
+
353
+ ```ruby
354
+ carousel = UI::FBCarousel.new(CAROUSEL)
355
+ show(carousel)
356
+ ```
357
+
358
+ Here is the the result:
359
+
360
+ ![carousel](./docs/carousel.png)
361
+
362
+ ### Image Attachment
363
+
364
+ You can send an image to the user. Note that image won't have any text, but you can send a regular message along with it.
365
+
366
+
367
+ ```ruby
368
+ img_url = 'https://unsplash.it/600/400?random'
369
+ image = UI::ImageAttachment.new(img_url)
370
+ show(image)
371
+ ```
372
+
373
+ ## Other events
374
+
375
+ Events other then `message` and `postback` are currently not supported.
376
+
377
+ # Deployment
378
+
379
+ Once you have designed your bot and tested in on localhost, it's time to send it to the cloud, so it live its life without being tethered to your machine. Assuming you already have a Heroku account and Heroku CLI tools installed, here's pretty much the whole process:
380
+
381
+ ```bash
382
+ $ git init # if haven't done before
383
+ $ git add . && git commit -m "Ready to deploy!"
384
+ $ heroku create YOUR_APP_NAME
385
+ $ heroku config:set ACCESS_TOKEN=your_own_page_token
386
+ $ heroku config:set VERIFY_TOKEN=your_own_verify_token
387
+ $ git push heroku master
388
+ ```
389
+
390
+ Now don't forget to go back to your Facebook developer console and change the address of your webhook from your ngrok's URL to the Heroku one. That's it!
391
+
392
+ ### :tada: You're live! :tada:
393
+
394
+ ## Coming next
395
+
396
+ * Proper implementation of logging (for now it's just `p` and `puts` statements sprinkled around the code) with ability to choose a logging level.
397
+
398
+ * More powerful DSL for parsing user input and binding it to commands.
399
+
400
+ * NLP integration with Wit.AI (that allow for more things then the built-in NLP capabilities of Messenger platform, including a wrapper around Wit's interactive learning methods) is in the works and will be added to the gem some time in 2018...
401
+
402
+ ## Contributing
403
+
404
+ Im a still a relatively new Ruby developer (I started coding in 2015 while looking for a break from my 10+ years career as a TV reporter) and this is my first attempt at OSS. Any contribution will be more than welcome! Rubotnik is still in the early stage of development, so it's your chance to make a difference!
405
+
406
+ ## Development
407
+
408
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests (currently project has no tests, but you're welcome to land a helping hand). You can also run `bin/console` for an interactive prompt that will allow you to experiment.
409
+
410
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
411
+
412
+ ## License
413
+
414
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "rubotnik"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
Binary file
data/docs/carousel.png ADDED
Binary file
Binary file
data/exe/rubotnik ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubotnik/cli'
3
+ require 'rubotnik/generator'
4
+ Rubotnik::CLI.start
@@ -0,0 +1,17 @@
1
+ module Rubotnik
2
+ module Autoloader
3
+ def self.root
4
+ Dir.pwd
5
+ end
6
+
7
+ def self.load(folder)
8
+ Dir[
9
+ File.expand_path(
10
+ File.join(root, folder)
11
+ ) + "/**/*.rb"
12
+ ].each do |file|
13
+ require_relative file
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ require 'thor'
2
+ require 'rubotnik'
3
+
4
+ module Rubotnik
5
+ class CLI < Thor
6
+
7
+ desc "new", "Generate bot project at PATH"
8
+ def new(path = nil)
9
+ generator = Rubotnik::Generator.new
10
+ generator.destination_root = path
11
+ generator.invoke_all
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ module Commands
2
+ # The contents of this module will be overridden by Commands inside host app
3
+ end;
@@ -0,0 +1,34 @@
1
+ require 'thor'
2
+ require 'thor/group'
3
+ require 'rubotnik'
4
+
5
+ class Rubotnik::Generator < Thor::Group
6
+ include Thor::Actions
7
+ desc 'Generate a new filesystem structure'
8
+
9
+ def self.source_root
10
+ File.dirname(__FILE__) + '/../../templates'
11
+ end
12
+
13
+ def create_file_structure
14
+ copy_file 'config.ru', 'config.ru'
15
+ copy_file 'Gemfile', 'Gemfile'
16
+ copy_file '.env', '.env'
17
+ copy_file 'puma.rb', 'config/puma.rb'
18
+ copy_file 'Procfile', 'Procfile'
19
+ copy_file 'commands.rb', 'bot/commands/commands.rb'
20
+ copy_file 'location.rb', 'bot/commands/location.rb'
21
+ copy_file 'ui_examples.rb', 'bot/commands/ui_examples.rb'
22
+ copy_file 'bot.rb', 'bot/bot.rb'
23
+ copy_file 'profile.rb', 'bot/profile.rb'
24
+ copy_file '.gitignore', '.gitignore'
25
+ end
26
+
27
+ #
28
+ # def create_git_files
29
+ # copy_file 'gitignore', '.gitignore'
30
+ # create_file 'images/.gitkeep'
31
+ # create_file 'text/.gitkeep'
32
+ # end
33
+
34
+ end
@@ -0,0 +1,71 @@
1
+ require 'httparty'
2
+ require 'json'
3
+
4
+ module Rubotnik
5
+ module Helpers
6
+ # Mixed-in methods become private
7
+ module_function
8
+
9
+ GRAPH_URL = 'https://graph.facebook.com/v2.8/'.freeze
10
+
11
+ # abstraction over Bot.deliver to send messages declaratively and directly
12
+ def say(text = 'What was I talking about?', quick_replies: [], user: @user)
13
+ message_options = {
14
+ recipient: { id: user.id },
15
+ message: { text: text }
16
+ }
17
+ if quick_replies && !quick_replies.empty?
18
+ message_options[:message][:quick_replies] = UI::QuickReplies
19
+ .build(*quick_replies)
20
+ end
21
+ Bot.deliver(message_options, access_token: ENV['ACCESS_TOKEN'])
22
+ end
23
+
24
+ def show(ui_element, user: @user)
25
+ ui_element.send(user)
26
+ end
27
+
28
+ def next_command(command)
29
+ @user.assign_command(command)
30
+ end
31
+
32
+ def stop_thread
33
+ @user.reset_command
34
+ end
35
+
36
+ def text_message?
37
+ @message.respond_to?(:text) && !@message.text.nil?
38
+ end
39
+
40
+ def message_contains_location?
41
+ @message.attachments && @message.attachments.first['type'] == 'location'
42
+ end
43
+
44
+ # Get user info from Graph API. Takes names of required fields as symbols
45
+ # https://developers.facebook.com/docs/graph-api/reference/v2.2/user
46
+ def get_user_info(*fields)
47
+ str_fields = fields.map(&:to_s).join(',')
48
+ url = GRAPH_URL + @user.id + '?fields=' + str_fields + '&access_token=' +
49
+ ENV['ACCESS_TOKEN']
50
+ begin
51
+ return call_graph_api(url)
52
+ rescue
53
+ puts "Couldn't access URL" # logging
54
+ return false
55
+ end
56
+ end
57
+
58
+ def call_graph_api(url)
59
+ @message.typing_on
60
+ response = HTTParty.get(url)
61
+ @message.typing_off
62
+ case response.code
63
+ when 200
64
+ puts "User data received from Graph API: #{response.body}" # logging
65
+ return JSON.parse(response.body, symbolize_names: true)
66
+ else
67
+ return false
68
+ end
69
+ end
70
+ end
71
+ end