Ginger - the Grocery Bot

Made by Swarna Srimal

Found in Prototyping Bots

Ginger is a SlackBot that can help anyone keep grocery lists easily so that you never end up in the supermarket not knowing what you need. She also suggests interesting recipes from the things you 'mark done' on your list - keep discovering what to get from the supermarket and what to cook in an endless loop with your friend, Ginger.

0

Who is Ginger?

Ginger is a Slackbot to maintain your grocery list. You can just slack her @gin about any additions and updates on your grocery list and she promptly keeps your list in check. Better still, when you mark things done on your list, she rewards you with a great recipe that you could cook from the things you bought.

What can she do?

Ginger is for anyone who wishes to be organised about their grocery list. If you feel lost at times in a super market wondering what you actually need, Ginger is for you.

You can ask Ginger to:

  • Add to your list by slacking her (for example) ‘add eggs, butter, tuna, bread, kale’
  • Edit things on your grocery list by slacking her (for example) ‘edit 1 to large eggs’
  • Delete things on your list by slacking her (for example) ‘delete 2’
  • Ask her to show your list at any time by slacking her ‘show list’
  • Mark things done on your list by slacking her ‘mark done 3,4’

When you mark things done on your list, she makes the update promptly and rewards you with a great recipe using (in this example) bread or kale.

Ginger’s personality

Ginger is smart, efficient and organized. You will notice that when you interact with her. She’s prompt with all her job and ensures there’s no confusion. She is always forgiving of any errors that a person might make - for example if you say ‘delete 14’ she will immediately check with you - ‘did you mistype?’ - allowing you to re-enter. She’s that friend who you go to to sort things out - calm and composed, always there for you but still keeps you from messing things up.

She’s also a food lover, so she loves sharing recipes with the things you bought. Well, she also takes pride in doing just a bit more than what you asked!

Watch Ginger in action

0
0

How does Ginger work?

Ginger integrates two APIs and a few internal databases to function:

  • Slack's API to post message in response to the user.
  • It uses Yummly's API to query recipes for any one randomly selected item that the user marks done.
  • User database, grocery list database and done items database to store information about which user has what items in their grocery list and what was marked done.

Data Diagram

This is the structure of the databases and APIs lying underneath Ginger's working:

0

Workflow

This is the current workflow for Ginger:

0

The Code behind Ginger

Ginger has been coded in Ruby on the Sinatra framework.

Following are the Gems used for Ginger:

0
source 'https://rubygems.org'

gem 'sinatra'
gem 'json'
gem 'shotgun'
gem "rake"
gem 'activerecord'
gem 'sinatra-activerecord' # excellent gem that ports ActiveRecord for Sinatra
gem 'activesupport'

gem 'haml'
gem 'slack-ruby-client'
gem 'httparty'


# to avoid installing postgres use 
# bundle install --without production

group :development, :test do
  gem 'sqlite3'
  gem 'dotenv'
end

group :production do
  gem 'pg'
end
Click to Expand
0

Following is the main application app.rb for Ginger:

0
require "sinatra"
require 'sinatra/activerecord'
require 'rake'
require 'active_support/all'
require "active_support/core_ext"

require 'haml'
require 'json'
require 'slack-ruby-client'
require 'httparty'


# ----------------------------------------------------------------------

# Load environment variables using Dotenv. If a .env file exists, it will
# set environment variables from that file (useful for dev environments)
configure :development do
  require 'dotenv'
  Dotenv.load
end


# require any models 
# you add to the folder
# using the following syntax:
# require_relative './models/<model_name>'
#require_relative './models/team'

Dir["./models/*.rb"].each {|file| require file }

Dir["./helpers/*.rb"].each {|file| require file }


helpers Sinatra::CommandsHelper


# enable sessions for this project
enable :sessions

# ----------------------------------------------------------------------
#     ROUTES, END POINTS AND ACTIONS
# ----------------------------------------------------------------------

get "/" do
  haml :index
end

get "/about" do
  "Hi! This is Ginger. I am a bot that helps you keep a grocery list. I also share interesting recipes that you can cook with the grocery items you buy."
end

# ----------------------------------------------------------------------
#     OAUTH
# ----------------------------------------------------------------------

# This will handle the OAuth stuff for adding our app to Slack
# https://99designs.com/tech-blog/blog/2015/08/26/add-to-slack-button/
# check it out here. 

# basically after clicking on the add to slack button 
# it'll return back with a code as a parameter in the query string
# e.g. https://yourdomain.com/oauth?code=92618588033.110206495095.452e860e77&state=

# we need to make a POST request to Slack's API to get a more
# permanent token that we can use to make requests to the API
# https://slack.com/api/oauth.access 
# read also: http://blog.teamtreehouse.com/its-time-to-httparty

get "/oauth" do 
  
  code = params[ :code ]
  
  slack_oauth_request = "https://slack.com/api/oauth.access"
  
  if code 
    response = HTTParty.post slack_oauth_request, body: {client_id: ENV['SLACK_CLIENT_ID'], client_secret: ENV['SLACK_CLIENT_SECRET'], code: code}
    
    puts response.to_s
    
    # We can extract lots of information from this web hook... 
    
    access_token = response["access_token"]
    team_name = response["team_name"]
    team_id = response["team_id"]
    user_id = response["user_id"]
        
    incoming_channel = response['incoming_webhook']['channel']
    incoming_channel_id = response['incoming_webhook']['channel_id']
    incoming_config_url = response['incoming_webhook']['configuration_url']
    incoming_url = response['incoming_webhook']['url']
    
    bot_user_id = response['bot']['bot_user_id']
    bot_access_token = response['bot']['bot_access_token']
    
    # wouldn't it be useful if we could store this? 
    # we can... 
    
    team = Team.find_or_create_by( team_id: team_id, user_id: user_id )
    team.access_token = access_token
    team.team_name = team_name
    team.raw_json = response.to_s
    team.incoming_channel = incoming_channel
    team.incoming_webhook = incoming_url
    team.bot_token = bot_access_token
    team.bot_user_id = bot_user_id
    team.save
    
    # finally respond... 
    "CourseBot Slack App successfully installed!"
    
  else
    401
  end
  
end

# If successful this will give us something like this:
# {"ok"=>true, "access_token"=>"xoxp-92618588033-92603015268-110199165062-deab8ccb6e1d119caaa1b3f2c3e7d690", "scope"=>"identify,bot,commands,incoming-webhook", "user_id"=>"U2QHR0F7W", "team_name"=>"Programming for Online Prototypes", "team_id"=>"T2QJ6HA0Z", "incoming_webhook"=>{"channel"=>"bot-testing", "channel_id"=>"G36QREX9P", "configuration_url"=>"https://onlineprototypes2016.slack.com/services/B385V4V8E", "url"=>"https://hooks.slack.com/services/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"}, "bot"=>{"bot_user_id"=>"U37HMQRS8", "bot_access_token"=>"xoxb-109599841892-oTaxqITzZ8fUSdmMDxl5kraO"}


# ----------------------------------------------------------------------
#     OUTGOING WEBHOOK
# ----------------------------------------------------------------------

post "/events" do 
  request.body.rewind
  raw_body = request.body.read
  puts "Raw: " + raw_body.to_s
  
  json_request = JSON.parse( raw_body )

  # check for a URL Verification request.
  if json_request['type'] == 'url_verification'
      content_type :json
      return {challenge: json_request['challenge']}.to_json
  end

  if json_request['token'] != ENV['SLACK_VERIFICATION_TOKEN']
      halt 403, 'Incorrect slack token'
  end

  respond_to_slack_event json_request
  
  # always respond with a 200
  # event otherwise it will retry...
  200
  
end


# THis will look a lot liks this: 
# {
#   "token": "9GCx7G3WrHix7EJsP818YOVB",
#   "team_id": "T2QJ6HA0Z",
#   "api_app_id": "A36PS6J72",
#   "event": {
#     "type": "message",
#     "user": "U2QHR0F7W",
#     "text": "g ddf;gkl;d fkg;ldfkg df",
#     "ts": "1480296595.000007",
#     "channel": "D37HZB04D",
#     "event_ts": "1480296595.000007"
#   },
#   "type": "event_callback",
#   "authed_users": [
#     "U37HMQRS8"
#   ]
# }


# TO TEST THIS LOCALLY USE THIS ... 
# DON"T INCLUDE IT IN DEVELOPMENT"

# CALL AS FOLLOWS
# curl -X POST http://127.0.0.1:9393/test_event -F token=9GCx7G3WrHix7EJsP818YOVB -F team_id=T2QJ6HA0Z -F event_type=message -F event_user=U2QHR0F7W -F event_channel=D37HZB04D -F event_ts=1480296595.000007 -F event_text='g ddf;gkl;d fkg;ldfkg df' 

post "/test_event" do

  if params['token'] != ENV['SLACK_VERIFICATION_TOKEN']
      halt 403, 'Incorrect slack token'
  end
  
  team = Team.find_by( team_id: params[:team_id] )
  
  # didn't find a match... this is junk! 
  return if team.nil?
  
  # see if the event user is the bot user 
  # if so we shoud ignore the event
  return if team.bot_user_id == params[:event_user]
  
  event = Event.create( team_id: params[:team_id], type_name: params[:event_type], user_id: params[:event_user], text: params[:event_text], channel: params[:event_channel ], timestamp: Time.at(params[:event_ts].to_f) )
  event.team = team 
  event.save
  
  client = team.get_client
  
  content_type :json
  return event_to_action client, event 
  
end

# ----------------------------------------------------------------------
#     SLASH COMMANDS
# ----------------------------------------------------------------------

# TEST LOCALLY LIKE THIS:
# curl -X POST http://127.0.0.1:9393/queue_status -F token=9GCx7G3WrHix7EJsP818YOVB -F team_id=T2QJ6HA0Z 

post "/queue_status" do
  
  
  # check it's valid
  if ENV['SLACK_VERIFICATION_TOKEN'] == params[:token]
    
    team_id = params[:team_id]
    channel_name = params[:channel_name]
    user_name = params[:user_name]
    text = params[:text]
    
    current_queue = OfficeHoursQueue.where( team_id: team_id ) 
    attachments = []
  
    if current_queue.empty?
      message = "There's no one in the queue. Type `queue me` to add."
    else
      message = "There's #{ current_queue.length } people in the queue. Here's the next few:"
      current_queue.limit(3).each_with_index do |q, index|
        attachments << get_queue_attachment_for( q )
      end
    end

    # specify the return type as 
    # json
    content_type :json
    {text: message, attachments: attachments, response_type: "ephemeral" }.to_json
    
    
  else
    content_type :json
    {text: "Invalid Request", response_type: "ephemeral" }.to_json

  end
  
end


# ----------------------------------------------------------------------
#   METHODS
#   Add any custom methods below
# ----------------------------------------------------------------------

private

# for example 
def respond_to_slack_event json
  
  # find the team 
  team_id = json['team_id']
  api_app_id = json['api_app_id']
  event = json['event']
  event_type = event['type']
  event_user = event['user']
  event_text = event['text']
  event_channel = event['channel']
  event_ts = event['ts']
  
  team = Team.find_by( team_id: team_id )
  
  # didn't find a match... this is junk! 
  return if team.nil?
  
  # see if the event user is the bot user 
  # if so we shoud ignore the event
  return if team.bot_user_id == event_user
  
  event = Event.create( team_id: team_id, type_name: event_type, user_id: event_user, text: event_text, channel: event_channel , timestamp: Time.at(event_ts.to_f) )
  event.team = team 
  event.save
  
  client = team.get_client
  
  event_to_action client, event 
  
end
Click to Expand
0

Following is the helper file that handles all the command responses Ginger gives :

0
module Sinatra
  module CommandsHelper

    # ------------------------------------------------------------------------
    # =>   MAPS THE CURRENT EVENT TO AN ACTION
    # ------------------------------------------------------------------------
    
    def event_to_action client, event
      
      puts event
      puts "Formatted Text: #{event.formatted_text}"
      
      return if event.formatted_text.nil?
      is_admin = is_admin_or_owner client, event
    
      
      # ------------------------------------------------------------------------
      # Hi Commands
      # ------------------------------------------------------------------------
      if ["hi", "hey", "hello"].any? { |w| event.formatted_text.starts_with? w }
        client.chat_postMessage(channel: event.channel, text: "Hi! I am Ginger. I can help you keep a grocery list.\n You can say `add` , `edit`, `delete`, `mark done`or `show list` ", as_user: true)
      
      # ------------------------------------------------------------------------
      # Add Commands
      # ------------------------------------------------------------------------
      elsif event.formatted_text == "add" 
        client.chat_postMessage(channel: event.channel, text:"Tell me one or multiple things you want to add to your grocery list. \n `Example: add potato, milk carton, cherries, chips` ", as_user: true )
      
      elsif event.formatted_text.starts_with? "add" 
        # we want to take the add part away
        # and then the rest we wanna add to our database
        items = event.formatted_text.gsub( "add", "" ).strip
        items.split( "," ).each do |item|
        listed = GroceryList.create( item_name: item  )
        listed.item_quantity = 1
        listed.save 
      end 
        # print the list
        all_items = GroceryList.order(:id) 
        grocery_list = ""
        all_items.each_with_index do |item, index|
        grocery_list += "*#{ index+ 1 }.* #{ item.item_name } \n"
      end
      client.chat_postMessage(channel: event.channel, text: "I've added that to you list.\n*Your updated list is as follows:*\n" + grocery_list  , as_user: true )
     
     # ------------------------------------------------------------------------
     #Edit Commands
     # ------------------------------------------------------------------------
     elsif event.formatted_text == "edit" 
     # print the list
     all_items = GroceryList.order(:id)
     grocery_list = ""
     all_items.each_with_index do |item, index|
       grocery_list += "*#{ index+ 1 }.* #{ item.item_name } \n"
      end
     client.chat_postMessage(channel: event.channel, text: "\n*Your grocery list is as follows:*\n" + grocery_list +"\nWhich one do you want to *edit*? \n`Example: edit 4 to potatoes`"  , as_user: true )
     
     elsif event.formatted_text.starts_with? "edit"
      # we want to take the edit part away
      # and then the rest we want to use for editing
      input  = event.formatted_text.gsub( "edit", "" ).strip
      input_words = input.split
      #getting the index number user wants to edit
      input_index = input_words[0].to_i - 1
      #removing the index number from string
      input_words.shift
      all_items = GroceryList.order(:id)
        #checking if the input exists in the list at all
        if input_index<=0 or input_index>all_items.length
          text = "\nI think you asked me to edit something that's not on your list. \nYou can say, `edit 1 to eggs`"
        #checking if user has followed 'to' syntax
        elsif input_words[0].to_s == "to"
          #removing the 'to' to get just the new edited entry
          input_words.shift
          grocery_string = input_words.join(" ")
          #saving the edit
          all_items[input_index].item_name = grocery_string
          all_items[input_index].save     
          #display edited list 
          grocery_list = ""
          edited_list = GroceryList.order(:id)
          edited_list.each_with_index do |item, index|
            grocery_list += "*#{ index+ 1 }.* #{ item.item_name } \n"
          end
          text = "\nSure, I made the edit! *Your grocery list now looks like this:*\n" + grocery_list  
        else
          text = "\nI can make the edit right away. Say something like  ' *edit* 1 *to* eggs '"
        end
      client.chat_postMessage(channel: event.channel, text: text , as_user: true )
      
      # ------------------------------------------------------------------------ 
      #Delete Commands
      # ------------------------------------------------------------------------
      elsif event.formatted_text == "delete" 
      # print the list
      all_items = GroceryList.order(:id)
      grocery_list = ""
      all_items.each_with_index do |item, index|
        grocery_list += "*#{ index+ 1 }.* #{ item.item_name } \n"
      end
      client.chat_postMessage(channel: event.channel, text: "\n*Your grocery list is as follows:*\n" + grocery_list +"\n\nWhich one/ones do you want to *delete*? \n`Example: delete 4`"  , as_user: true )
      
      elsif event.formatted_text == "delete all"
      client.chat_postMessage(channel: event.channel, text: "Are you sure you want to delete you *entire list*? Say `yes delete` or `no don't`"  , as_user: true )
      
      elsif ["yes delete", "yes, delete.", "yes, delete","yes del","yes please"].any? { |w| event.formatted_text.starts_with? w }
      GroceryList.destroy_all
      client.chat_postMessage(channel: event.channel, text: "Okay, I just cleaned up your entire grocery list. You're good to start a fresh new list!" , as_user: true )
      
      elsif ["no don't", "no dont", "no, don't","no, dont","dont", "no"].any? { |w| event.formatted_text.starts_with? w } 
      client.chat_postMessage(channel: event.channel, text: "Don't worry! I won't delete anything till you're absolutely sure you are done with this one!" , as_user: true )
      
      elsif event.formatted_text.starts_with? "delete"
      text= ""
      error_text =""
      # we want to take the add part away
      # and then the rest we wanna add to our database
      items = event.formatted_text.gsub( "delete", "" ).strip
      all_items = GroceryList.order(:id)
      flag = 0
      items.split( "," ).each do |num|
        #checking if the input exists in the list at all
        if num.to_i>0 and num.to_i<all_items.length+1
          i= (num.to_i)-1
          all_items[i].destroy
          flag = 1
        else
          #error text if one or more are not valid indexes
          error_text = "I think one or more of those entries that you asked for don't exist on your list.Please check if you mistyped."
        end
      end
      redefined_list = GroceryList.order(:id)
      #if no error
      if error_text == ""
      grocery_list = ""
      redefined_list.each_with_index do |item, index|
        grocery_list += "*#{ index+ 1 }.* #{ item.item_name } \n"
      end
      text= "Okay, I've deleted that from your list. \n*Your updated list is as follows:*\n" + grocery_list
      #if there are some indexes that exist and some that don't
      elsif flag ==1
        grocery_list = ""
        redefined_list.each_with_index do |item, index|
          grocery_list += "*#{ index+ 1 }.* #{ item.item_name } \n"
        end
      text= "I've deleted some things you asked me to. \nBut " + error_text + " \n*Your updated list is as follows:*\n" + grocery_list
      #if all indexes don't exist in the list
      else
        text = error_text
      end
      client.chat_postMessage(channel: event.channel, text: text, as_user: true )
 
      # ------------------------------------------------------------------------
      #Show Command
      # ------------------------------------------------------------------------
      
      elsif ["show", "list", "display", "grocery list", "show list" " show grocery list", "grocery"].any? { |w| event.formatted_text.starts_with? w }
      # print the list
      if GroceryList.all.empty?
        client.chat_postMessage(channel: event.channel, text: "\nYour grocery list is empty and clean to start adding things right away! \n For example, you can say `add eggs`" , as_user: true )
      else  
        all_items = GroceryList.order(:id)
        grocery_list = ""
        all_items.each_with_index do |item, index|
          grocery_list += "*#{ index+ 1 }.* #{ item.item_name } \n"
          end
      client.chat_postMessage(channel: event.channel, text: "\n*Your grocery list :*\n" + grocery_list  , as_user: true )
      end
      
      # ------------------------------------------------------------------------
      #Mark Done Command
      # ------------------------------------------------------------------------
      elsif event.formatted_text == "mark done"
        client.chat_postMessage(channel: event.channel, text: "Awesome, you got things done?\nTell me which ones and I'll strike them out right away. \nYou can say `mark done 4,5`"  , as_user: true )

      elsif event.formatted_text.starts_with? "mark done"
        input = event.formatted_text.gsub( "mark done", "" ).strip
        done_list = ""
        done_item = ""
        recipe_ingredient = ""
        recipe = ""
        #variables for customizing text received for different error scenarios in mark done
        flag =0
        error_text = ""
        text =""
        
        all_items = GroceryList.order(:id)        
        input.split( "," ).each do |num|
          #checking if all input indexes exist in the list
          if num.to_i>0 and num.to_i<all_items.length+1
            i= (num.to_i)-1
            done_item = all_items[i][:item_name]
            recipe_ingredient = done_item
            done_list += done_item+"\n"
            all_items[i].destroy
            flag = 1
          else
            #error text if one or more are not valid indexes
            error_text = "I think one or more of those entries that you asked for don't exist on your list.Please check if you mistyped."
            end
          end
        #if no error    
        if error_text == ""
          remaining_items = GroceryList.order(:id)
          remaining_list = ""
          remaining_items.each_with_index do |item, index|
            remaining_list += "*#{ index+ 1 }.* #{ item.item_name } \n"
          end
          yummly_app_id = '19a71c98'
          yummly_app_key = 'a3bdaf1de2b3acbb2abcb8755b214874'
          # Accessing Yummly API
          base_url = "https://api.yummly.com/v1/api/recipes?_app_id=#{yummly_app_id}&_app_key=#{yummly_app_key}&requirePictures=true"
          base_url+="&allowedIngredient="+recipe_ingredient
          url = URI.parse(URI.encode(base_url.strip))
          response = HTTParty.get(url)
          data = JSON.parse(response.body)
          
          # this gets me the matches element from the JSON response
          recipe_options = data["matches"]
          recipe_names = "Try this recipe: \n"
          recipe = recipe_options.sample(1).first
          name = recipe["recipeName"]
          image = recipe["smallImageUrls"].first
          url = "http://www.yummly.co/#recipe/" + recipe["id"]
          ingredients = recipe["ingredients"].join( ", ")
          recipe_names += "*#{name}*" + " \n#{url } \n#{image} \nMade with: #{ingredients}"
          text= "Awesome! I noted that the following are done:\n" + done_list + "*Now your remaining list looks like this:*\n "+ remaining_list +"\nAlso, Here's an awesome recipe that uses "+ recipe_ingredient+ ".\n" +recipe_names
        else
          text = "Umm, I'd be happy to mark things done on the list, but some of those things don't exist on your list. Please check if you mistyped."
        end
        client.chat_postMessage(channel: event.channel, text: text, as_user: true ) 
      
      # ------------------------------------------------------------------------
      #Thanks Response
      # ------------------------------------------------------------------------  
      elsif event.formatted_text.starts_with? "thank"
        text = ""
        responses =["You're very welcome", "You're most welcome", "Welcome!", "Oh, mention not :)" ]
        text = responses.sample
        client.chat_postMessage(channel: event.channel, text: text, as_user: true)
      
      # ------------------------------------------------------------------------
      #Appreciation Response
      # ------------------------------------------------------------------------
      
      elsif ["awesome", "great", "brilliant", "good", "okay", ":+1:"].any? { |w| event.formatted_text.starts_with? w }
        client.chat_postMessage(channel: event.channel, text: "Happy to help. I'm always there for you!", as_user: true)
      # ------------------------------------------------------------------------
      #Help Response
      # ------------------------------------------------------------------------  
      elsif event.formatted_text.starts_with? "help"
      client.chat_postMessage(channel: event.channel, text: "I am Ginger, a grocery list bot.\n You can say `add` , `edit`, `delete`, `mark done`or `show list` " , as_user: true)
      
      # ------------------------------------------------------------------------
      #Error Response
      # ------------------------------------------------------------------------                   
      else
            # ERROR Commands
            # not understood or an error
            client.chat_postMessage(channel: event.channel, text: "I didn't get that. If you're stuck, type `help` to find my commands.",  as_user: true)
          end
    end

    # ------------------------------------------------------------------------
    #  GETS USEFUL INFO FROM SLACK
    # ------------------------------------------------------------------------
    
    def get_user_name client, event
      # calls users_info on slack
      info = client.users_info(user: event.user_id ) 
      info['user']['name']
    end
    
    def is_admin_or_owner client, event
      # calls users_info on slack
      info = client.users_info(user: event.user_id ) 
      info['user']['is_admin'] || info['user']['is_owner']
    end
  
  end
  
end
Click to Expand
0

A more detailed documentation is available on Github.


Future Possibilities

Ginger could be expanded for better functionality in the following ways:

Ginger could be integrated with APIs that allow ordering of groceries. This would make Ginger a one stop solution for those who want to maintain grocery lists, shop for groceries online and receive recipes.

Ginger's forgiving capabilities can be expanded with an undo command that allows the user to go back one or more steps. This would be useful in accidental cases of deletion, addition, editing and ordering.

Another interesting feature for extended user engagement could be a food quiz. In the scenario when the user has not interacted for (may be) a week, the bot could push a small visual quiz about food preference (example: a vegetarian dish over a non vegetarian one, or a fish dish over a beef one) and these preferences could be stored in an additional table which could be used to filter better recipes for the user.

These are some of the features that were plotted on a effort/priority matrix which could be used as reference for future development:

0

Reflections

The Programming for Online Prototyping course was an interesting journey which allowed us to explore multiple platforms and 'learn by doing' in an unknown territory.

I learnt that while bots may seem simple, the execution behind them can be fairly complex. Even more interesting was trying to build a personality for the bot - thinking of all the responses from a personality perspective was helpful in providing unique user experiences through different bots.

Also, during API selection it was critical to study carefully what the APIs provide as compared to their websites and which functionalities are free and which ones are paid for. If one is working with a fairly new API, things are likely to change quickly and affect how the app works.

If I have one takeaway from this course, it's this: practice let's you make more intelligent mistakes rather than the easier ones. One should never be afraid of an error - fixing errors is more learning than writing code.

References

  • The Slack API has brilliant documentation and can be found here.
  • The Yummly API allows one to search through recipes by ingredients, by dietary restrictions and many other parameters. It's documentation can be found here.
  • Other discoveries I made which were very useful while making this bot can be found at the tail end of this document here.
  • The icons for this bot were sourced from the noun project. Here I credit the creators for the icons I used: Woman created by Hea Poh Lin, List created by Ben Markoch, Cook book anonymous, add by Created by Alexander Smith.

Thanks

I would like to thank Professor Daragh Byrne for constant encouragement and extremely detailed instructions through class lectures, demos, exercises and online resources that he has created. For anyone building bots and other web based zero UI services, his online repo is an invaluable resource:

https://github.com/daraghbyrne/onlineprototypes2016repo/tree/master/code-samples

x
Share this Project

Courses

49-714 Programming for Online Prototypes

· 14 members

An introduction to rapidly prototyping web-based products and services. This 7-week experience will teach students the basics of web development for online services. Specifically, well focus on lig...more


Focused on
Skills
Tools
About

Ginger is a SlackBot that can help anyone keep grocery lists easily so that you never end up in the supermarket not knowing what you need.
She also suggests interesting recipes from the things you 'mark done' on your list - keep discovering what to get from the supermarket and what to cook in an endless loop with your friend, Ginger.

Created

December 17th, 2016