Time across seven seas…

It has been rightly said:

Time is of your own making
Its clock ticks in your head.
The moment you stop thought
Time too stops dead.

loklak_org_sticker

Hence to keep up with evolving times, Loklak has now introduced a new service for “time”.

The recently developed API provides the current time and day at the location queried by the user.

The /api/locationwisetime.json API scrapes the results from timeanddate.com using our favourite JSoup as it provides a very convenient API for extracting and manipulating data, scrape and parse HTML from a given URL.

In case of multiple locations with the same name, countries are then also provided along-with corresponding day and time wrapped up as a JSONObject.

A sample query could then be something like: http://loklak.org/api/locationwisetime.json?query=london

Screenshot from 2016-08-17 14:28:28

 

When implemented as a console service, this API can be used along-with our our dear SUSI by utilising the API Endpoints like: http://loklak.org/api/console.json?q=SELECT * FROM locationwisetime WHERE query=’berlin’;

Screenshot from 2016-08-17 14:50:58

LocationWiseTimeService.java for reference:


/**
 *  Location Wise Time
 *  timeanddate.com scraper
 *  Copyright 27.07.2016 by Jigyasa Grover, @jig08
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *  
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *  
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program in the file lgpl21.txt
 *  If not, see <http://www.gnu.org/licenses/>.
 */

package org.loklak.api.search;

import java.io.IOException;

import javax.servlet.http.HttpServletResponse;

import org.json.JSONArray;
import org.json.JSONObject;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.loklak.server.APIException;
import org.loklak.server.APIHandler;
import org.loklak.server.AbstractAPIHandler;
import org.loklak.server.Authorization;
import org.loklak.server.BaseUserRole;
import org.loklak.server.Query;
import org.loklak.susi.SusiThought;
import org.loklak.tools.storage.JSONObjectWithDefault;

public class LocationWiseTimeService extends AbstractAPIHandler implements APIHandler {

	private static final long serialVersionUID = -1495493690406247295L;

	@Override
	public String getAPIPath() {
		return "/api/locationwisetime.json";
	}

	@Override
	public BaseUserRole getMinimalBaseUserRole() {
		return BaseUserRole.ANONYMOUS;

	}

	@Override
	public JSONObject getDefaultPermissions(BaseUserRole baseUserRole) {
		return null;
	}

	@Override
	public JSONObject serviceImpl(Query call, HttpServletResponse response, Authorization rights,
			JSONObjectWithDefault permissions) throws APIException {
		String query = call.get("query", "");
		return locationWiseTime(query);
	}

	public static SusiThought locationWiseTime(String query) {
		
		Document html = null;

		JSONArray arr = new JSONArray();

		try {
			html = Jsoup.connect("http://www.timeanddate.com/worldclock/results.html?query=" + query).get();
		} catch (IOException e) {
			e.printStackTrace();
		}

		Elements locations = html.select("td");
		int i = 0;
		for (Element e : locations) {
			if (i % 2 == 0) {
				JSONObject obj = new JSONObject();
				String l = e.getElementsByTag("a").text();
				obj.put("location", l);
				String t = e.nextElementSibling().text();
				obj.put("time", t);
				arr.put(obj);
			}
			i++;
		}
		
		SusiThought json = new SusiThought();
		json.setData(arr);
		return json;
	}

}

 

Hope this helps, and worth the “time” 😛

Feel free to ask questions regarding the above code snippet, shall be happy to assist.

Feedback and Suggestions welcome 🙂

Time across seven seas…

Bot integrations of Susi on Online Social Media: Slack

In my past few posts, I have explained the use of Susi in detail. We have come to see Susi as an intelligent chat bot cum search engine, which answers Natural Language queries, and has a large dataset to support it thanks to the various sites we scrape from, the APIs we integrate, and also, the additional services that we make (like the TwitterAnalysisService I talked about). All of these make Susi an excellent chat service.

So now the question comes up: how do we increase its reach?

This is where bot integration comes up. Services like Messenger (Facebook), Google Hangouts, Slack, Gitter etc have a large number of user chatting on their platform, but in addition, they also added an additional service of bot users. These users, when messaged about related queries, answer those queries to the user. We have recently seen a very interesting example of this, when the White House used FB Messenger Bots for people to reach out to President Obama (link). This makes users get quick and instant replies to specific queries, and also, bot integrations on these big platforms make more and more people connect with the bot and its maintainers too.

That is why we believed it would be amazing if Susi were integrated onto these platforms as a bot, so that people realise all the things it is able to do. Now, we need to implement these.

As Sudheesh must have spoken about, we are following a system of maintaining all the bots on one index.js file, all of the bots post to different routes, and we deploy this file and the npm requirements in the package.json so that all run concurrently. Keeping this in mind, I developed the Slack Bot for Susi.

I actually developed the bot both in Python and node, but we will only be using node because of the easiness of deployment. For those who wish to check out the Python code, head over here. The Slack API usage remains the same though.

The main part of our bot will be the Slack RTM (Real Time Messaging) API. It basically reads all the conversation going on, and reports every message in a specified format, like:


{
    "id": 1,
    "type": "message",
    "channel": "C024BE91L",
    "text": "Hello world"
}

There are other parameters also included, like username. More info on all the parameters can be found here.

So this is the API schema. We will be using an npm package called slackbots for implementing our bot. Slackbots gives an easy way of interfacing with the RTM API, so that we can focus on implementing the Susi API in the bot without having to worry much about the RTM. You could read Slackbots’ documentation here.

For making the bot, first go here, register your bot, and get the access token. We will need this token to make authorised requests to the API. For keeping it in a secure place, store it as an environment variable:

export SLACK_TOKEN=<access token>

Now comes the main code. Create a new node project using npm init. Once the package.json is created, execute the following commands:


npm install --save requests
npm install --save slackbots

This install the slackbots and the requests packages in our project. We will need requests to make a connection with the Susi API on http://loklak.org/api/susi.json.

Now we are all set to use slackbots and write our code. Make a new file index.js (add this to the package.json as well). Here’s the code for our slackbot.


'use strict';
/* global require, process, console */
var request = require('request');
var SlackBot = require('slackbots');
var slack_token = process.env.SLACK_TOKEN; //accessing the slack token from environment
var slack_bot = new SlackBot({
	token: slack_token, 
	name: 'susi'
});

slack_bot.on('message', function(data){
	var slackdata = data;
	var msg, channel, output, user;
	if(Object.keys(slackdata).length > 0){
		if('text' in slackdata && slackdata['username'] != 'susi'){
			msg = data['text'];
			channel = data['channel']
		}
		else {
			msg = null;
			channel = null;
		}
	}
	if(msg != null && channel !=null){
		var botid = ':' 
		if (msg.split(" ")[0] != botid){
			//do nothing
		} else{
			var apiurl = 'http://loklak.org/api/susi.json?q=' + msg;
			request(apiurl, function (error, response, body) {
				if (!error && response.statusCode === 200) {
					var data = JSON.parse(body);
					if(data.answers[0].actions.length == 1){
						var susiresponse = data.answers[0].actions[0].expression;
						slack_bot.postMessage(channel, susiresponse);
					} else if(data.answers[0].actions.length == 2 && data.answers[0].actions[1].type == "table"){
						slack_bot.postMessage(channel, data.answers[0].actions[0].expression + " (" + data.answers[0].data.length + " results)");
						for(var i = 0; i < data.answers[0].data.length; ++i){
							var response = data.answers[0].data[i];
							var ansstring = "";
							for(var resp in response){
								ansstring += (resp + ": " + response[resp] + ", ");
							}
							slack_bot.postMessage(channel, ansstring);
						}
					}
				}
			});
		}
	}
});

Let’s go over this code bit by bit. We instantiate SlackBots using our token first. Then, the line slack_bot.on('message', function(data) triggers the RTM API. We first get the message in the conversation, check if its JSON is empty or not. Also, our bot should only reply when the user asks, it should not reply to the queries of itself (because RTM continuously reads input, so even the bot’s replies come under it, so we don’t want the bot to react to its own replies lest we get an infinite loop). This check is done through:


if(Object.keys(slackdata).length > 0){
		if('text' in slackdata && slackdata['username'] != 'susi'){
			msg = data['text'];
			channel = data['channel']
		}
		else {
			msg = null;
			channel = null;
		}
	}

We also get the text message and the channel to post the message into.

Next, we check for an empty message. If there is a message, we check if the message starts with @susi: (my bot was named susi, and the bot id came from the RTM API itself, I hardcoded it). We should only query the Susi API in such a case where the message starts with @susi. And once that check is done, we query the Susi API, and the response is data.answers[0].actions[0].expression (except when it’s a table, then we use data.answers[0].data). Once we get what we need to send, we use SlackBot’s postMessage method, and post the message onto the channel using the RTM API. That’s what the rest of the code does.


if(msg != null && channel !=null){
		var botid = ':' 
		if (msg.split(" ")[0] != botid){
			//do nothing
		} else{
			var apiurl = 'http://loklak.org/api/susi.json?q=' + msg;
			request(apiurl, function (error, response, body) {
				if (!error && response.statusCode === 200) {
					var data = JSON.parse(body);
					if(data.answers[0].actions.length == 1){
						var susiresponse = data.answers[0].actions[0].expression;
						slack_bot.postMessage(channel, susiresponse);
					} else if(data.answers[0].actions.length == 2 && data.answers[0].actions[1].type == "table"){
						slack_bot.postMessage(channel, data.answers[0].actions[0].expression + " (" + data.answers[0].data.length + " results)");
						for(var i = 0; i < data.answers[0].data.length; ++i){
							var response = data.answers[0].data[i];
							var ansstring = "";
							for(var resp in response){
								ansstring += (resp + ": " + response[resp] + ", ");
							}
							slack_bot.postMessage(channel, ansstring);
						}
					}
				}
			});
		}
	}
});

This completes the bot. When you shoot it up from your terminal using node index.js, or deploy it, it will work perfectly.

This can now be used by a wide range of people, and everyone can see all that Susi can do. 🙂

We are still in the process of making bots. FB, Telegram and Slack bots have been made till now, and we will be making more. Feedback, as usual, is welcome. 🙂

Bot integrations of Susi on Online Social Media: Slack

Architectural design for supporting susi on multiple messaging services

Susi has been evolving and learning more every single day leveraging the billion+ tweets that the loklak server has indexed. The next important step would be to hookup Susi’s capabilities in a fashion that the world can easily use. A best friend powered by the data that’s scraped on every single platform available. With this in mind, we first dug deep into the facebook messenger potentially exposing susi’s capabilities to more than a billion people on the planet but as we scale and move to other agents like telegram, slack etc.., We needed some architectural changes to minimize the code duplication as well as the number of resources that we consume. In this blog post, i’ll walk you all through the design decision for the architecture planned to expose Susi to the world.

This is a detailed architecture for running all the different messaging services that we wish to accomplish in the near future. Chat / Messengers are becoming something very important and many a times the very first app that one opens up on their smart phone. It’s very important that the data in Loklak be made sense of to the people out there and learn intelligently. Susi is a great step in the process towards using the twitter data and data from other scrapers and data sources so that information can be given to people querying for it. Running a lot of services is really simple when we set up each one of the individually on a separate server but running the same code on multiple servers to just cater to one single messenger like platform ? Nah not a great idea.

Almost all of the messenger platforms be it Facebook Messenger, Telegram, Slack or anything else run on the same method, event driven and use webhooks. The idea here is to have multiple of these webhooks, and create validation endpoints for the same in case they use the GET request validations of the server like how Facebook does before verifying, At the same time many of them need SSL Certificates so that the service can be setup. This part is simplified by the heroku hosting and the default SSL that it provides for every application URL it provides.

All the services residing in the same server host/application can be used to share the common query library i.e. making the requests to /api/susi.json and returning the corresponding json or the answer entry which is available at body.answers[0].actions[0].expression , There’s a lot more information and modular architecture that can be targeted during the cleanup of each of these services into the required folders by using routing from the index.js for the same. In such a system, the index.js behaves as a proxy layer forwarding the requests to the corresponding service agent rather than scanning through the entire index.js file as it is now. So the application structure over time would look like this.

Messenger Architecture Diagram

|- Common\QueryBuilder.js (Common Library to be used across)
|- Facebook/
|--------\facebook.js
|--------\supportFiles.js
|- Slack/
|- Telegram/
|- Susi's Chat Interface/
|- Other Services ...,
|- index.js (Route to required agent)
Architectural design for supporting susi on multiple messaging services

Social Media Analysis using Loklak (Part 3)

In my last two blog posts, I spoke about the TwitterAnalysis Servlet, and how the data actually comes into it through scraping methods. For TwitterAnalysis though, there was one thing that was missing: Susi integration, which I’ll cover in this blog post.

Given that the TwitterAnalysis servlet is basically a Social Media profile analyser, we could definitely get a lot of useful statistics from it. As covered earlier, we are getting likes, retweets, hashtag statistics, sentiment analysis, frequency charts etc. Now, to get this working on Susi, we need to build queries which can use these statistics and give the user valuable information.

First off, the serviceImpl method needs to be changed to return a SusiThought object. SusiThought is a JSONObject which processes the query (does keyword extraction etc), uses the APIs to get an answer to the query, and returns the answer along with the count of answers (incase of a table). SusiThought is what triggers the entire Susi mechanism, so the first thing for Susi integration is to convert TwitterAnalysis to return a SusiThought object:


@Override
	public JSONObject serviceImpl(Query call, HttpServletResponse response, Authorization rights,
			JSONObjectWithDefault permissions) throws APIException {
		String username = call.get("screen_name", "");
		String count = call.get("count", "");
		TwitterAnalysisService.request = call.getRequest();
		return showAnalysis(username, count);
	}
public static SusiThought showAnalysis(String username, String count) {

//rest of the code as explained in last blog post
//SusiThought is a JSONObject so we simply copy-paste the serviceImpl code here

}

Once this is done, we write up the queries in the susi_cognition.

As you may have read in my last blog post, TwitterAnalysis gives the Twitter Profile analysis of a user, it’s basically statistics, so we could have a lot of queries regarding this. So these are the rules I implemented, they are self-explanatory on reading the example fields:


{
			"keys"   :["tweet frequency", "tweets", "month"],
			"score"  :2000,
			"example": "How many tweets did melaniatrump post in May 2016",
			"phrases":[ {"type":"pattern", "expression":"* tweet frequency of * in *"},
				{"type":"pattern", "expression":"* tweets did * post in *"},
				{"type":"pattern", "expression":"* tweets did * post in the month of *"}
			],
			"process":[ {"type": "console", "expression": "SELECT yearwise[$3$] AS count FROM twitanalysis WHERE screen_name='$2$' AND count='1000';"}],
			"actions":[ {"type": "answer", "select": "random", "phrases":[
				"$2$ tweeted $count$ times in $3$"
			]}]
		},
		{
			"keys"   :["tweet frequency", "tweets", "post", "at"],
			"score"  :2000,
			"example": "How many tweets did melaniatrump post at 6 PM",
			"phrases":[ {"type":"pattern", "expression":"* tweet frequency of * at *"},
				{"type":"pattern", "expression":"* tweets did * post at *"}
			],
			"process":[ {"type": "console", "expression": "SELECT hourwise[$3$] AS count FROM twitanalysis WHERE screen_name='$2$' AND count='1000';"}],
			"actions":[ {"type": "answer", "select": "random", "phrases":[
				"$2$ tweeted $count$ times at $3$"
			]}]
		},
		{
			"keys"   :["tweet frequency", "tweets", "post", "on"],
			"score"  :2000,
			"example": "How many tweets did melaniatrump post on Saturdays",
			"phrases":[ {"type":"pattern", "expression":"* tweet frequency of * on *s"},
				{"type":"pattern", "expression":"* tweets did * post on *s"},
				{"type":"pattern", "expression":"* tweet frequency of * on *"},
				{"type":"pattern", "expression":"* tweets did * post on *"}
			],
			"process":[ {"type": "console", "expression": "SELECT daywise[$3$] AS count FROM twitanalysis WHERE screen_name='$2$' AND count='1000';"}],
			"actions":[ {"type": "answer", "select": "random", "phrases":[
				"$2$ tweeted $count$ times on $3$"
			]}]
		},
		{
			"keys"   :["tweet frequency", "chart"],
			"score"  :2000,
			"example": "Show me the yearwise tweet frequency chart of melaniatrump",
			"phrases":[ {"type":"pattern", "expression":"* the * tweet frequency chart of *"}],
			"process":[ {"type": "console", "expression": "SELECT $2$ FROM twitanalysis WHERE screen_name='$3$' AND count='1000';"}],
			"actions":[ {"type": "answer", "select": "random", "phrases":[
				"This is the $2$ frequency chart of $3$"
			]}, {"type":"table"}]
		},
		{
			"keys"   :["tweet type", "post", "a"],
			"score"  :2000,
			"example": "How many times did melaniatrump post a video",
			"phrases":[ {"type":"pattern", "expression":"* did * post a *"}],
			"process":[ {"type": "console", "expression": "SELECT $3$ AS count FROM twitanalysis WHERE screen_name='$2$' AND count='1000';"}],
			"actions":[ {"type": "answer", "select": "random", "phrases":[
				"$2$ posted a $3$ $count$ times"
			]}]
		},
		{
			"keys"   :["tweet activity", "likes", "count"],
			"example": "How many likes does melaniatrump have in all",
			"score"  :2000,
			"phrases":[ {"type":"pattern", "expression":"* likes does * have *"}
			],
			"process":[ {"type": "console", "expression": "SELECT likes_count AS count FROM twitanalysis WHERE screen_name='$2$' AND count='1000';"}],
			"actions":[ {"type": "answer", "select": "random", "phrases":[
				"$2$ has $count$ likes till now"
			]}]
		},
		{
			"keys"   :["tweet activity", "likes", "maximum"],
			"example": "What is the maximum number of likes that melaniatrump got",
			"score"  :2000,
			"phrases":[ {"type":"pattern", "expression":"* maximum * likes that * got"}
			],
			"process":[ {"type": "console", "expression": "SELECT max_likes FROM twitanalysis WHERE screen_name='$3$' AND count='1000';"}],
			"actions":[ {"type": "answer", "select": "random", "phrases":[
				"Here you go"
			]}, {"type": "table"}]
		},
		{
			"keys"   :["tweet activity", "likes", "average"],
			"example": "What is the average number of likes that melaniatrump gets",
			"score"  :2000,
			"phrases":[ {"type":"pattern", "expression":"* average * likes that * gets"}
			],
			"process":[ {"type": "console", "expression": "SELECT average_number_of_likes AS count FROM twitanalysis WHERE screen_name='$3$' AND count='1000';"}],
			"actions":[ {"type": "answer", "select": "random", "phrases":[
				"$3$ gets $count$ likes on an average"
			]}]
		},
		{
			"keys"   :["tweet activity", "likes", "frequency"],
			"score"  :2000,
			"example": "How many times did melaniatrump get 0 likes",
			"phrases":[ {"type":"pattern", "expression":"* * have * likes"},
				{"type":"pattern", "expression":"* * get * likes"}
			],
			"process":[ {"type": "console", "expression": "SELECT likes_chart[$3$] AS count FROM twitanalysis WHERE screen_name='$2$' AND count='1000';"}],
			"actions":[ {"type": "answer", "select": "random", "phrases":[
				"$2$ got $3$ likes, $count$ times"
			]}]
		},
		{
			"keys"   :["tweet activity", "likes", "frequency", "chart"],
			"score"  :2000,
			"example": "Show me the likes frequency chart of melaniatrump",
			"phrases":[ {"type":"pattern", "expression":"* likes frequency chart * *"}
			],
			"process":[ {"type": "console", "expression": "SELECT likes_chart FROM twitanalysis WHERE screen_name='$3$' AND count='1000';"}],
			"actions":[ {"type": "answer", "select": "random", "phrases":[
				"Here is the likes frequency chart"
			]}, {"type": "table"}]
		},
		{
			"keys"   :["tweet activity", "retweets", "count"],
			"score"  :2000,
			"example": "How many retweets does melaniatrump have in all",
			"phrases":[ {"type":"pattern", "expression":"* retweets does * have *"}
			],
			"process":[ {"type": "console", "expression": "SELECT retweets_count AS count FROM twitanalysis WHERE screen_name='$2$' AND count='1000';"}],
			"actions":[ {"type": "answer", "select": "random", "phrases":[
				"$2$ has $count$ retweets till now"
			]}]
		},
		{
			"keys"   :["tweet activity", "retweets", "maximum"],
			"score"  :2000,
			"example": "What is the maximum number of retweets that melaniatrump got",
			"phrases":[ {"type":"pattern", "expression":"* maximum * retweets that * got"}
			],
			"process":[ {"type": "console", "expression": "SELECT max_retweets FROM twitanalysis WHERE screen_name='$3$' AND count='1000';"}],
			"actions":[ {"type": "answer", "select": "random", "phrases":[
				"Here you go"
			]}, {"type": "table"}]
		},
		{
			"keys"   :["tweet activity", "retweets", "average"],
			"score"  :2000,
			"example": "What is the average number of retweets that melaniatrump gets",
			"phrases":[ {"type":"pattern", "expression":"* average * retweets that * gets"}
			],
			"process":[ {"type": "console", "expression": "SELECT average_number_of_retweets AS count FROM twitanalysis WHERE screen_name='$3$' AND count='1000';"}],
			"actions":[ {"type": "answer", "select": "random", "phrases":[
				"$3$ gets $count$ retweets on an average"
			]}]
		},
		{
			"keys"   :["tweet activity", "retweets", "frequency"],
			"score"  :2000,
			"example": "How many times did melaniatrump get 0 retweets",
			"phrases":[ {"type":"pattern", "expression":"* * have * retweets"},
				{"type":"pattern", "expression":"* * get * retweets"}
			],
			"process":[ {"type": "console", "expression": "SELECT retweets_chart[$3$] AS count FROM twitanalysis WHERE screen_name='$2$' AND count='1000';"}],
			"actions":[ {"type": "answer", "select": "random", "phrases":[
				"$2$ got $3$ retweets, $count$ times"
			]}]
		},
		{
			"keys"   :["tweet activity", "retweets", "frequency", "chart"],
			"score"  :2000,
			"example": "Show me the retweet frequency chart of melaniatrump",
			"phrases":[ {"type":"pattern", "expression":"* retweet frequency chart * *"}
			],
			"process":[ {"type": "console", "expression": "SELECT retweets_chart FROM twitanalysis WHERE screen_name='$3$' AND count='1000';"}],
			"actions":[ {"type": "answer", "select": "random", "phrases":[
				"Here is the retweets frequency chart"
			]}, {"type": "table"}]
		},
		{
			"keys"   :["tweet activity", "hashtags", "count"],
			"score"  :2000,
			"example": "How many hashtags has melaniatrump used in all",
			"phrases":[ {"type":"pattern", "expression":"* hashtags has * used *"}
			],
			"process":[ {"type": "console", "expression": "SELECT hashtags_used_count AS count FROM twitanalysis WHERE screen_name='$2$' AND count='1000';"}],
			"actions":[ {"type": "answer", "select": "random", "phrases":[
				"$2$ has used $count$ hashtags till now"
			]}]
		},
		{
			"keys"   :["tweet activity", "hashtags", "maximum"],
			"score"  :2000,
			"example": "What is the maximum number of hastags that melaniatrump used",
			"phrases":[ {"type":"pattern", "expression":"* maximum * hashtags that * used"}
			],
			"process":[ {"type": "console", "expression": "SELECT max_hashtags FROM twitanalysis WHERE screen_name='$3$' AND count='1000';"}],
			"actions":[ {"type": "answer", "select": "random", "phrases":[
				"Here you go"
			]}, {"type": "table"}]
		},
		{
			"keys"   :["tweet activity", "hashtags", "average"],
			"score"  :2000,
			"example": "What is the average number of hashtags that melaniatrump uses",
			"phrases":[ {"type":"pattern", "expression":"* average * hashtags that * uses"}
			],
			"process":[ {"type": "console", "expression": "SELECT average_number_of_hashtags_used AS count FROM twitanalysis WHERE screen_name='$3$' AND count='1000';"}],
			"actions":[ {"type": "answer", "select": "random", "phrases":[
				"$3$ uses $count$ hashtags on an average"
			]}]
		},
		{
			"keys"   :["tweet activity", "hashtags", "frequency"],
			"score"  :2000,
			"example": "How many times did melaniatrump use 20 hashtags",
			"phrases":[ {"type":"pattern", "expression":"* * use * hashtags"}
			],
			"process":[ {"type": "console", "expression": "SELECT hashtags_chart[$3$] AS count FROM twitanalysis WHERE screen_name='$2$' AND count='1000';"}],
			"actions":[ {"type": "answer", "select": "random", "phrases":[
				"$2$ used $3$ hashtags, $count$ times"
			]}]
		},
		{
			"keys"   :["tweet activity", "hashtags", "frequency", "chart"],
			"score"  :2000,
			"example": "Show me the hashtag frequency chart of melaniatrump",
			"phrases":[ {"type":"pattern", "expression":"* hashtag frequency chart * *"}
			],
			"process":[ {"type": "console", "expression": "SELECT hashtags_chart FROM twitanalysis WHERE screen_name='$3$' AND count='1000';"}],
			"actions":[ {"type": "answer", "select": "random", "phrases":[
				"Here is the hashtags frequency chart"
			]}, {"type": "table"}]
		},
		{
			"keys"   :["tweet content", "language", "frequency"],
			"score"  :2000,
			"example": "How many tweets did melaniatrump write in English?",
			"phrases":[ {"type":"pattern", "expression":"* * write in *"},
				{"type":"pattern", "expression":"* * post in *"},
				{"type":"pattern", "expression":"* of * were written in *"}
			],
			"process":[ {"type": "console", "expression": "SELECT languages[$3$] AS count FROM twitanalysis WHERE screen_name='$2$' AND count='1000';"}],
			"actions":[ {"type": "answer", "select": "random", "phrases":[
				"$2$ posted $count$ tweets in $3$"
			]}]
		},
		{
			"keys"   :["tweet content", "language", "analysis", "chart"],
			"score"  :2000,
			"example": "Show me the language analysis chart of melaniatrump",
			"phrases":[ {"type":"pattern", "expression":"* language analysis chart * *"}
			],
			"process":[ {"type": "console", "expression": "SELECT languages FROM twitanalysis WHERE screen_name='$3$' AND count='1000';"}],
			"actions":[ {"type": "answer", "select": "random", "phrases":[
				"Here is the language analysis chart"
			]}, {"type": "table"}]
		},
		{
			"keys"   :["tweet content", "sentiment", "analysis", "chart"],
			"score"  :2000,
			"example": "Show me the sentiment analysis chart of melaniatrump",
			"phrases":[ {"type":"pattern", "expression":"* sentiment analysis chart * *"}
			],
			"process":[ {"type": "console", "expression": "SELECT sentiments FROM twitanalysis WHERE screen_name='$3$' AND count='1000';"}],
			"actions":[ {"type": "answer", "select": "random", "phrases":[
				"Here is the sentiment analysis chart"
			]}, {"type": "table"}]
		}

(PS: no points for guessing why melaniatrump is there in the examples 😉 )

As has been explained before, I simply write up an expression consisting of parameters and some core words which are hardcoded, and I then fetch up the parameters using $x$ (x = parameter number). These queries can actually give a whole lot of statistics regarding a user’s activity and activity on his profile, so it is definitely a whole lot useful for a chatbot.

Now, to end this, we need a way to process these queries. Enter ConsoleService. Notice that all the process['expression'] SQL queries are of the type:

SELECT <something> FROM twitanalysis where screen_name = '<parameter_of_username>' AND count = '1000';

I have taken count as 1000 because as mentioned in my last blog post, the scraper scrapes and displays a maximum of 1000 results at a time, so I wish to maximise the range.

Converting the above SQL generalised query to regex, we get this form:

SELECT\\h+?(.*?)\\h+?FROM\\h+?twitanalysis\\h+?WHERE\\h+?screen_name\\h??=\\h??'(.*?)'\\h+?AND\\h+?count\\h??=\\h??'(.*?)'\\h??;

The \h is for a whitespace that may occur, and the random queries are just expressed using (.*) (random character selection in regex). Since we have specific parameters (as described above) that we use in our SQL queries, we encapsulate these random character selections into groups.

Now, we need to compile this regex, and point to what needs to be done. This is done in ConsoleService.


dbAccess.put(Pattern.compile("SELECT\\h+?(.*?)\\h+?FROM\\h+?twitanalysis\\h+?WHERE\\h+?screen_name\\h??=\\h??'(.*?)'\\h+?AND\\h+?count\\h??=\\h??'(.*?)'\\h??;"), (flow, matcher) -> {
            SusiThought json = TwitterAnalysisService.showAnalysis(matcher.group(2), matcher.group(3));
            SusiTransfer transfer = new SusiTransfer(matcher.group(1));
            json.setData(transfer.conclude(json.getData()));
            return json;
        });

We basically compile the regex, and feed it to a bifunction (lambda lingo for a function that takes in two params). We take in the groups using matcher.group and since you saw above, the SusiThought object in TwitterAnalysis takes in screen_name and count, so we take them in using the matcher and feed them to the static function showAnalysisinside the TwitterAnalysis servlet. We then get back the JSON. This completes the procedure. TwitterAnalysis is now integrated with the Susi API. 🙂

In my next blog posts, I’ll talk about Bot integrations for Susi, and a Slack Bot for Susi I made, and then I’ll move to Susi monetisation using Amazon API. Feedback is welcome 🙂

Social Media Analysis using Loklak (Part 3)

Setting up Susi’s capabilities on Facebook Messenger

Facebook’s messenger platform is a great way to reach out to a lot of people from a page that one owns on facebook. The messenger services reach out to almost 900 million people who use the system, that’s a humongous set of people to which Susi’s capabilities could be reached out to if integrated with the facebook messenger and that’s exactly what has been done. Susi is the AI System running in Loklak which contains rules to run the required scrapers or fetch the information directly from facebook. To set this up, we created a new repository deployed on heroku called asksusi_messengers which is going to be a collection of such messenger integrations i.e. to Facebook, Slack, Whatsapp, Telegram etc.., So that the power of mobile and messaging services can be used to make Susi smarter and ways in which people can consume Susi’s capabilities.

Considering the real time nature of people and messages, we have used node.js to take requests by using a webhook on facebook which subscribes to the changes or any event that triggers from the asksusisu facebook page. So here’s the way they really work. Messenger bots uses a web server to process messages it receives or to figure out what messages to send. You also need to have the bot be authenticated to speak with the web server and the bot approved by Facebook to speak with the public. This means that we create the messenger service with facebook apps and then register the endpoint so that facebook can trigger that URL endpoint more like a webhook so that the messages that were sent by the user can be sent to Susi’s AI Service.

Using express this is pretty simple and can be accomplished by the following piece of code that listens to the root of the application.
app.get('/', function (req, res) {
res.send('Susi says Hello.');
});

This ensures that the application is active for a user trying to hit the GET endpoint of the service and as a security method, the rest of the application endpoints need to be on a POST endpoints so that the application’s responses are secure and not anyone can send requests without the required tokens.

Facebook needs an SSL based hostname so that the service can be binded for this. The fastest way this can be spun up is by using the heroku deployments, Hence we use a ProcFile with the contents in it as

web: node index.js

Then configure the application on facebook developers with the given name and setup a messenger webhook over there to send an event to the heroku server that you just deployed, Added to this add your own token which you need to remember and put up on heroku too. After this you will receive a page access token which you can temporarily save somewhere and later push to heroku as a configuration. You can read this configuration by doing and facebook’s verification works.


var token = process.env.FB_PAGE_ACCESS_TOKEN;

// for facebook verification
app.get('/webhook/', function (req, res) {
if (req.query['hub.verify_token'] === 'this_is_my_top_secret_token_that_i_know') {
res.send(req.query['hub.challenge']);
}
res.send('Error, wrong token');
});

You can then receive the information from facebook’s events as follows on a POST request endpoint to the webhook

// to post data
app.post('/webhook/', function (req, res) {
var messaging_events = req.body.entry[0].messaging;
}

The messaging_events gets the required information from facebook in

event.message

and

event.message.text

objects of the triggered webhook event. The query to susi is then constructed


// Construct the query for susi
var queryUrl = 'http://loklak.org/api/susi.json?q='+encodeURI(text);
var message = '';
// Wait until done and reply
request({
url: queryUrl,
json: true
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
message = body.answers[0].actions[0].expression;
sendTextMessage(sender, message);
} else {
message = 'Oops, Looks like Susi is taking a break, She will be back soon';
sendTextMessage(sender, message);
}
});

And voila we have the susi facebook page automatically replying powered by Loklak’s capabilities of Susi. Currently susi can reply with the text messages as well as image responses.

Susi's facebook integration
Susi’s facebook integration
Setting up Susi’s capabilities on Facebook Messenger

Susi support for Loklak APIs

Here at Loklak, we are striving continuously for innovation. Continuing with this trend, we recently launched ‘Susi – The chat bot’. Please refer to this previous blog post by Damini.

Along with the chat bot, Susi query support was added to Loklak Python and PHP APIs. Susi can be queried from localhost as well as other online loklak peers.

Susi API function added to Python API(as shown below). See full implementation here.

def susi(self, query=None):
   """Hits Susi with the required query and returns back the susi response"""
   susi_application = 'api/susi.json'
   url_to_give = self.baseUrl + susi_application
   self.query = query
   if query:
      params = {}
      params['q'] = self.query
      return_to_user = requests.get(url_to_give, params=params)
      if return_to_user.status_code == 200:
          return return_to_user.json()
      else:
          return_to_user = {}
          return_to_user['error'] = ('Looks like there is a problem in susi replying.')
          return json.dumps(return_to_user)
      else:
          return_to_user = {}
          return_to_user['error'] = ('Please ask susi something.')
          return json.dumps(return_to_user)

A sample usage of Susi API in python could be:

from loklak import Loklak
query = "Hi I am Zeus"
l = Loklak()
result = l.susi(query)
print result

Susi integration with PHP API(see below). See full implementation here.

public function susi($query=null) {
	$this->requestURL = $this->baseUrl . '/api/susi.json';
	$this->query = $query;
	if($query) {
		$params = array('q'=>$this->query);
		$request = Requests::request($this->requestURL, array('Accept' => 'application.json'), $params);
		if ($request->status_code == 200) {
			return json_encode($request, true);
		}
		else {
			$request = array();
			$error = "Looks like Susi is not replying.";
			$request['error'] = array_push($request, $error);
			return json_encode($request, true);
		}
	}
	else {
		$request = array();
		$error = "Please ask Susi something.";
		$request['error'] = array_push($request, $error);
		return json_encode($request, true);
	}
}

Sample usage of Susi API in PHP:

include('loklak.php');
$loklak = new Loklak(); 
$result = $loklak->susi('Hi I am Zeus');
$susiResponse = json_decode($result);
$susiResponse = $susiResponse->body;
$susiResponse = json_decode($susiResponse, true);
var_dump($susiResponse);

Tests for above-mentioned functions have been added to the respective API suite. Refer to this and this.

Try Social Universe Super Intelligence!

Ask questions, interact with it. I am pretty sure that you would like it!

Susi support for Loklak APIs

Susi Rule-Score Hierarchy Brainstorming

For Susi’s score system, we need a hierarchy to assign good score values to the rules. To do so we should develop a hierarchy to find an easy system that can be used to assign scores to new rules.

Please add your suggestions below, as you add your ideas we will change a score hierarchy suggestion below.

Preliminary Consideration: Patterns

We have two kinds of rules: such with patterns and others without. The meansing of such rules are:

with pattern(s):

  • (P+LR) variables in pattern should be used for retrieval in internal Susi’s log (reflection memory)
  • (P+IR) variables in pattern should be used for retrieval in internal databases
  • (P+ER) variables in pattern should be used for retrieval in external databases
  • (P+LS) variables in pattern should be stored in Susi’s memory to be used for reflection later
  • (P+IS) variables in pattern should be stored in internal databases to be used for retrieval later
  • (P+ES) variables in pattern should be stored in external databases to be used for retrieval later

without any pattern:

  • (P-D) default answers if no other rule applies
  • (P-O) overruling of rules which would apply, but should not

Secondary Consideration: Purpose

We have three kinds of purposes for Susi answers:

  • (/A) to answer on the users question
  • (/Q) to ask a question to the user in the context of an objective within Susi’s planning to do a conversation
  • (/R) to answer on an answer of the user within the planning of Susi to do a conversation. It appears clear that answers in the context of a Susi conversation strategy should have higher priority.

Combinations of Pattern and Purpose Considerations:

To combine the various Pattern and Purpose types, we write the abbreviations of these items together. For example, we want to answer on a question of the user “Are you happy” with “Yes!, Are you happy as well?” which would be an rule of type P-O/Q. The combination of the both consideration types give 8×3=24 possibilities.

Score Hierarchy

I believe there should be
– score(R) > score(Q) > score(A):
to do a steering of conversations within a conversation plan.
– score(P-O) > score(P+?) > score(P-D):
overruling of pattern-directed answers and default answers in case of pattern fail
– score(P+?S) > score(P+?R):
storing of information (= learning) is more important than answering
– score(P+L?) > score(P+I?) > score(P+E?):
using local information is primary above external information. Reflection is most important.

This produces the following order (with decreasing score, first line has highest sore):

– Overruling of patterns:
– R/P-O
– Q/P-O
– A/P-O

– Answer on an Answer of the user using patterns, possibly learning, otherwise retrieving data
– R/P+LS
– R/P+IS
– R/P+ES
– R/P+LR
– R/P+IR
– R/P+ER

– Asking the user a question with the purpose of learning with the users answer
– Q/P+LS
– Q/P+IS
– Q/P+ES
– Q/P+LR
– Q/P+IR
– Q/P+ER

– Just giving an answer to the question of the user
– A/P+LS
– A/P+IS
– A/P+ES
– A/P+LR
– A/P+IR
– A/P+ER

– Fail-over if no other rule apply to just answer anything, but try to start a new conversation
– R/P-D
– Q/P-D
– A/P-D

Susi Rule-Score Hierarchy Brainstorming

Susi chat interface with visualizations

Susi got few capabilities to visualize it’s response. She can respond by sharing links, showing analytics on pie charts and give you a list of bulleted data. So this post shows you on how these components are integrated into Susi.

The rules which are defined can give data in various compatible forms. It can give links, share some analytics in the form of percentages and certain list of data. For example, in the previous blog post on adding susi rules, we added a sample rule showing on how to add types of responses to susi. If you want more context on it, you can click here.

  • Susi taking responses from data: This type of response is in the form of a table. Susi can take the extra data under the data.answers[0].data , where the type if of table. The below is a sample JSON format from which the tabular data can be parsed.

Selection_251

From the above JSON the data under the answers object is being traced for the tabulated answers. This expression will get you the titles of the reddit articles.

Selection_252

The above response is for the following query

What are the reddit articles about loklak

This is Susi’s response on asksusi.

Selection_249

  • Susi answering using piecharts: Susi rules can also be defined in such a way that the response can give out a well formed pie chart. The data required for the piechart is defined and this can easily be interpreted using highcharts for giving a clear pie chart response. Here is sample JSON response for the following query.
Who will win the 2016 presidential election

Selection_234

The above JSON defines data for piecharts giving percentage and relevant name for that particular object. This is easy to interpret the json for defining the piecharts using highchart.js . The below is the sample code which was used to define the piecharts.

Selection_253

This is how the interface answers with piecharts.

Selection_247

Selection_248

  • So susi can also interpret links from the response and linkify them accordingly.

Here is the sample code on how Susi interprets the links from the response.

Selection_254

The links are linkified and this how Susi responses.

Selection_255

Stay tuned for more updates on Susi.

 

 

Susi chat interface with visualizations