I am currently building a small conversational bot, and I am struggling with the architecture. I use Facebook Messenger combined with Wit.ai's API and a Shopify store API. My rails app is the interface between those three APIs.
First, the rails app receives a message from Facebook. It transfers it to Wit, which sends back a message. If the user sent all the needed parameters to search the Shopify store, it will send a query to get some products, and transfer it afterwards to Facebook. If all the parameters aren't there, it will send other questions to Facebook (this is dealt with inside of Wit).
I did everything in a single controller, which I'm not comfortable with. The more the app is going to grow/the bot gets smarter, the more the controller will be huge and messy.
class MessengerBotController < ActionController::Base
def find_or_create_session(fbid, max_age: 5.minutes)
Session.find_by(["facebook_id = ? AND last_exchange >= ?", fbid, max_age.ago]) ||
Session.create(facebook_id: fbid, context: {})
end
def wit_request(msg, sender)
@actions = {
:say => -> (session_id, context, msg) {
session = Session.find(session_id)
session.update(context: context)
sender.reply({ text: msg })
},
:merge => -> (session_id, context, entities, msg) {
session = Session.find(session_id)
p entities
context["username"] = sender.get_profile[:body]["first_name"]
if entities["gender"]
if entities["gender"].first["value"] == "men"
context["gender"] = 263046279
elsif entities["gender"].first["value"] == "wom"
context["gender"] = 263046151
end
end
if entities["brand"]
context["brand"] = entities["brand"].first["value"]
end
if entities["style"]
context["style"] = entities["style"].first["value"]
end
@session.update(context: context)
p context
return context
},
:error => -> (session_id, context, error) {
p 'Oops I don\'t know what to do.'
},
:run_query => -> (session_id, context) {
session = Session.find(session_id)
products = Oj.load(RestClient.get "https://#{ENV['shopify_token']}@myshopifybot.myshopify.com/admin/products.json?collection_id=#{context['gender']}&brand=#{context['brand']}&product_type=#{context['style']}")
if context['gender'].nil? || context['brand'].nil? || context['style'].nil?
fb_request(@session.facebook_id, "I need more information")
else
products = Oj.load(RestClient.get "https://#{ENV['shopify_token']}@myshopifybot.myshopify.com/admin/products.json?collection_id=#{context['gender']}&brand=#{context['brand']}&product_type=#{context['style']}")
request_params = {
recipient: {id: session.facebook_id},
message: {
"attachment":{
"type":"template",
"payload":{
"template_type":"generic",
"elements":[
]
}
}
},
access_token: ENV["fb_token"]
}
products["products"].each do |h1|
request_params[:message][:attachment][:payload][:elements] << { "title": h1["title"],
"image_url": h1["images"].first["src"],
"subtitle":"",
"buttons":[
{
"type":"web_url",
"url":"#",
"title":"More info"
},
{
"type":"postback",
"payload": h1["id"],
"title":"Check stock"
},
{
"type":"postback",
"title":"Similar items",
"payload":"similar"
}
]
}
end
sender.reply(request_params)
end
return context
}
}
client = Wit.new ENV["wit_token"], @actions
end
def message(event, sender)
msg = event["message"]["text"]
sender_id = event["sender"]["id"]
session = find_or_create_session(sender_id)
session.update(last_exchange: Time.now)
wit_request(msg, sender)
client.run_actions session.id, msg, session.context
end
def postback(event, sender)
msg = event["postback"]["payload"]
sender_id = event["sender"]["id"]
session = find_or_create_session(sender)
session.update(last_exchange: Time.now)
wit_request(msg, sender)
client.run_actions session.id, payload, session.context
end
end
I'm especially struggling with the JSON/hash templates in the queries, that make the code difficult to read. Any advice/input on how to separate concerns, maybe in different controllers/helpers/methods would be much appreciated.