Deploy SUSI AI to Viber messenger

Prerequisites

  1. Basic knowledge about calling API’s and fetching data or posting data to the API.
  2. Node.js language.
  3. Github
  4. Heroku

FigArchitecture for running all different messaging services.

To integrate Susi AI chat to Viber, a public account is needed, messaging to which users can chat with Susi.

We need to have a webhook url. Webhook url is a url which serves our Node.js code i.e. the code we will write to serve requests from Viber and to respond back to it.

Whenever a user messages to the SUSI AI public account, these messages come as post requests to our webhook url. The url then requests Susi API to give an answer for the (question based) message received from Viber. The answer fetched from Susi API is sent to the messenger platform’s API by the webhook url, to show it to the user on Viber.

As said we need a public account for our chat bot. The steps to be followed can be seen from here (Steps 2 and 3).

The REST API helps to make applications follow a RESTful way. In this way, the requests and response are in the form of JSON objects. Any language can be used to make an application follow a RESTful way.

In this blog, I will be using Node.js language.

The Rest API Viber, is the document to be followed for integration of a chatbot to Viber. Let’s go through each of the steps:

  1. To call Susi API and fetch an answer from it for a query (‘hi’ in this case). Let’s first visit http://api.asksusi.com/susi/chat.json?q=hi from the browser. We will get a JSON object as follows:

The answer can be found as the value of the key named expression. In this case it is “Hallo!”.

To fetch the answer through coding, we can use this code snippet in Node js:

// including request module
var request = require(‘request’);

// setting options to make a successful call to Susi API.
var options = {
    method: 'GET',
    url: 'http://api.asksusi.com/susi/chat.json',
    qs: 
    {
        timezoneOffset: '-330',
        q:'hi'
    }
};

// A request to the Susi bot
request(options, function (error, response, body) {
    if(error)
        throw new Error(error);
    //answer fetched from susi
    ans = (JSON.parse(body)).answers[0].actions[0].expression;
}

The properties required for the call are set up through a json object (i.e. options). Pass the options object to our request function as its 1st parameter. The response by the API will be stored in ‘body’ variable. We need to parse this body, to be able to access the properties of that body object. Hence, fetching the answer from Susi API.

  1. Let’s set the webhook url for our Susi public account. The folder containing our Node.js code must be pushed to a repo in github. We need to do some changes to the default package.json file in our project.

This file has a code portion:

The “test” key and its value must be replaced with “start”: “node index.js” i.e. node followed by the name of the main file which has to accept the requests from Viber and responds to it. In my case it is index.js . So the resultant code of package.json file should look like this:

Now, even on the local system you can run the node application by running “npm start” command from our project folder.

  • Push this project to github.
  • Create a new heroku app for Node js, following the steps here.
  • Then link this app to the repository where you pushed your code. For reference, follow the steps 7 and 8 here.

To set a webhook for our account, we can use this code snippet.

var headerBody = { 
                     'cache-control': 'no-cache',
                     'content-type': 'application/json',
                     'x-viber-auth-token': 'YOUR_X_VIBER_AUTH_TOKEN'
                 };

var options = {
                  method: 'POST',
                  url: 'https://chatapi.viber.com/pa/set_webhook',
                  headers: headerBody,
                  body:
                       { 
                           url: 'YOUR_WEBHOOK_URL',
                           event_types: ['delivered', 'seen', 'failed', 'subscribed', 'unsubscribed','conversation_started']
                       },
                  json: true 
              };

// request to the chat api of viber.
request(options, function(error, res, body) {
    if (error)
        throw new Error(error);
    response.write("The status message - " + body.status_message);
    response.end(); 
});

To set the webhook url, we need to post info to the viber chat api i.e. https://chatapi.viber.com/pa/set_webhook. The headers key present in our options object must have the Viber authentication key in the object passed to it. So we have passed a headerBody object to it, which contains our x-viber-auth-token. This property helps Viber to set webhook url for the account that corresponds to this passed Viber authentication token. The format of the body of options object is according to this(as stated in the doc):

{  
    "url": "https://my.host.com",  
    "event_types": ["delivered", "seen", "failed", "subscribed", "unsubscribed", "conversation_started"]  
}

We can wrap up this code in a app.get() block. So that whenever we visit our webhook url from a browser, we initiate a get request. This request as seen below, initiates a request to the chat api of Viber to set this url as a webhook url for our Susi AI public account.

app.get('/',function(req, response){
    response.writeHead(200,{'content-type': 'text/plain'});
    response.write("To chat with Susi through Viber, visit this link - chats.viber.com/chatauto and click on the 'Have a look' button\n\n");

    // setting options to request the chat api of viber.
    var options = {
                      method: 'POST',
                      url:'https://chatapi.viber.com/pa/set_webhook',
                      headers: headerBody,
                      body: 
                      {
                          url:'https://intense-crag-83953.herokuapp.com',
                          event_types: ['delivered', 'seen', 'failed', 'subscribed', 'unsubscribed', 'conversation_started']
                      },
                      json: true 
    };

    // request to the chat api of viber.
    request(options, function(error, res, body) {
        if (error) 
            throw new Error(error);
        response.write("The status message received for set Webhook request is - " + body.status_message);
        response.end();
    });
});

Now after setting up a webhook url, the messages sent to our account will come as post requests to this webhook url.

Let’s work with some of the events, for which Viber callbacks our webhook url.

First, let’s talk about the message event request.

According to Viber, this will be the body of the request:

{
    "event": "message",
    "timestamp": 1457764197627,
    "message_token": 4912661846655238145,
    "sender": {
        "id": "01234567890A=",
        "name": "John McClane",
        "avatar": "http://avatar.example.com",
        "country": "UK",
        "language": "en",
        "api_version": 1
    },
    "message": {
       "type": "text",
       "text": "a message to the service",
       "media": "http://example.com",
       "location": {
          "lat": 50.76891,
          "lon": 6.11499
       },
       "tracking_data": "tracking data"
    }
}

We check if the value of ‘event’ key is equal to ‘message’:

app.post('/', function(req, response) {
    response.writeHead(200);

    // If user sends a message in 1-on-1 chat to the susi public account
    if(req.body.event === 'message'){

If it is, then the body of if block can be populated with the code to handle messages by the user.

Assuming we have a reply by Susi API in the ans variable. We can include this code further:

// setting options to request the chat api of viber.
    var options1 = {
                       method: 'POST',
                       url: 'https://chatapi.viber.com/pa/send_message',
                       headers: headerBody,
                       body: 
                            {
                                receiver: req.body.sender.id,
                                min_api_version: 1,
                                sender: 
                                       {
                                           name: 'Susi',
                                           avatar: '' 
                                       },
                                tracking_data: 'tracking data',
                                type: 'text',
                                text: ans 
                   },
                   json: true 
   };

// request to the chat api of viber.
request(options1, function (error1, res, body1) {
    if (error1)
        throw new Error(error1);
    console.log(body1);
});

This above code can help us send a response to the user. To send a message to the user, our body object in options1 must be similar to:

{
    "receiver": "01234567890A=",
    "min_api_version": 1,
    "sender": {
       "name": "John McClane",
       "avatar": "http://avatar.example.com"
    },
    "tracking_data": "tracking data",
    "type": "text",
    "text": "a message from pa"
}

receiver key should have a value of the id of the user to which message needs to sent, which will be shown on our account interface. The user id can be easily fetched from request by ‘req.body.sender.id’ as the reply is to be sent to the same user from where we received the message.

The sender key’s value is an object which indicates the sender’s name, which in our case is Susi. Also we can pass an avatar url along with the name.

The text key must have our answer as the value i.e. the ‘ans’ variable (in this case).

The whole code to accept the request, and reply accordingly:

// If user sends a message in 1-on-1 chat to the susi public account
if(req.body.event === 'message'){
    // Susi answer to a user message
    var ans;
    // setting options to request susi bot.
    var options1 = { 
                       method: 'GET',
                       url:'http://api.asksusi.com/susi/chat.json',
                       qs: { 
                            timezoneOffset: '-330',
                            q:req.body.message.text 
                           }
    };

    // A request to the Susi bot
    request(options1, function (error1, response1, body1) {
        if (error1)
            throw new Error(error1);
        // answer fetched from susi
        ans = (JSON.parse(body1)).answers[0].actions[0].expression;
        var options = {
                          method: 'POST',
                          url:'https://chatapi.viber.com/pa/send_message',
                          headers: headerBody,
                          body: 
                               {
                               receiver: req.body.sender.id,
                               min_api_version: 1,
                               sender: 
                                      {
                                           name: 'Susi',
                                           avatar: '' 
                                      },
                               tracking_data:'tracking data',
                               type: 'text',
                               text: ans 
                      },
        json: true 
    };

    // request to the chat api of viber.
    request(options, function (error, res, body) {
        if (error)
            throw new Error(error);
        console.log(body);
    });
}

The same way we can handle “conversation started” type of event:

if(req.body.event === 'conversation_started'){
    // Welcome Message
    var request = require("request");
    var options = {
                      method: 'POST',
                      url:'https://chatapi.viber.com/pa/send_message',
                      headers: headerBody,
                      body: 
                          {
                              receiver: req.body.user.id,
                              min_api_version: 1,
                              sender: 
                                    {
                                        name: 'Susi',
                                        avatar: '' 
                                    },
                              tracking_data: 'tracking data',
                              type: 'text',
                              text:'Hi from your favourite, Susi!' 
                          },
                      json: true 
    };

    request(options, function (error, res, body) {
        if (error)
            throw new Error(error);
        console.log(body);
    });
}

This way our Susi AI chatbot can reply to messages from Viber.

The repository which contains whole project of Susi AI’s integration to Viber can be found here.

Continue ReadingDeploy SUSI AI to Viber messenger

Creating Middle Section of Listing Page for loklak Apps Store

The Loklak applications website that is apps.loklak.org now has a fully functional store listing page where users and developers can find various information about the app they have selected including information about getting started with the app, use of the app and other relevant information. Developers can showcase their apps and promote their apps with promo image and preview images.

Before beginning with the actual topic let me provide a brief overview of the various components on the page. The page consists of a left sidebar which contains various categories for app filtering. There is a right column showing suggested apps so that users can easily view apps which are similar to the one they have chosen. Finally there is a middle section containing app promo image, app title, short description, author’s name, a carousel showing preview images, a getting started section, an app use section and an other relevant information section and finally some additional information.

The main feature and requirement of the middle section is that it needs to be dynamic, that is, it should dynamically show information for a given selected app. No content should be hardcoded.

Now apps.loklak.org is entirely a front end project, there is no centralised data base from where the content can be loaded. How to solve this problem? From where to get the data related to each app. Well, here comes in the app.json file present in each app. Each application present on apps.loklak.org contains an app.json file which contains some metadata about the apps. Now the app.json can be modified by the developer of the app to contain relevant information and references to assets which will be used for app store listing.

Getting started with the middle section page

At first, let us have a look at a sample app.json file.

{
  "@context":"http://schema.org",
  "@type":"SoftwareApplication",
  "permissions":"/api/search.json",
  "name":"sentimentVisualizer",
  "headline":"Sentiment Visualizer",
  "alternativeHeadline":"Tool for visualizing the sentiment of a tweet",
  "applicationCategory":"Visualizer",
  "applicationSubCategory":"Text Retrieval",
  "operatingSystem":"http://loklak.org",
  "promoImage":"promo.png",
  "appImages":["disp1.png", "disp2.png", "disp3.png"],
  "getStarted":"getStarted.md",
  "appUse":"appUse.md",
  "oneLineDescription":"Application to visualise sentiment related to tweets",
  "appSource": "https://github.com/fossasia/apps.loklak.org/tree/master/sentimentVisualizer",
  "contributors": [{"name": "Damini Satya", "url": "https://github.com/daminisatya"}],
  "techStack": ["HTML", "CSS", "AngularJs", "Bootstrap", "Loklak API"],
  "license": {"name": "LGPL 2.1", "url": "https://www.gnu.org/licenses/old-licenses/lgpl-2.1"},
  "version": "1.0",
  "updated": "June 14,2017",
  "author":{
    "@type":"Person",
    "name":"Damini Satya",
    "email":"daminisatya@gmail.com",
    "url":"https://github.com/daminisatya",
    "sameAs":"https://github.com/daminisatya"
  }
}

Each of the apps must have an app.json like the above one. As it can be seen, it contains all the necessary information for sore listing, like app name, version, oneLineDescription, etc. It also contains paths to important assets.

Before proceeding we need to know the name of the app which has been selected. This is obtained from the url as shown below.

var addr = window.location + "";
if (addr.indexOf("?") !== -1) {
    $scope.appName = addr.split("?")[1].split("=")[1];
}

Once we get the app name, next thing which we need to do is load the contents of app.json.
This is done using AngularJs’s http service.

$http.get($scope.appName + "/app.json").success(function (data) {
        $scope.appData = data;
        $scope.setupCarousel();
        $scope.getStarted();
        $scope.appUse();
        $scope.others();
});

A http ajax call is made to app.json of the corresponding app and the entire data is stored in appData.

Before moving to the subsequent function calls let us take a look at the corresponding html code which creates the top section.

<div class="app-image-and-info">
              <div class="app-image animated fadeInDown">
                <img ng-src="../{{selectedApp.name}}/promo.png" class="img-rounded image-loaded img-responsive">
              </div>
              <div class="app-info">
                <h2 class="app-name"> {{selectedApp.name}} </h2>
                <h4 class="app-headline"> {{appData.headline}} </h4>
                <h5 class="author"> <a href={{appData.author.url}} class="author-link">
                  by {{appData.author.name}} </a> </h5>
                <div class="short-desc"> {{appData.oneLineDescription}} </div>
                <a href="../{{selectedApp.name}}" class="try-now"> Try Now </a>
              </div>
            </div>

It inserts the promo image, app name, one line description, author name and link and a ‘Try now’ button to try the live app.

In the last JS code snippet (last to last snippet) we saw some function calls. The first one is setupCarousel.

$scope.setupCarousel = function () {
        var items = "";
        var active = "";
        for (var i = 0; i < $scope.appData.appImages.length; i++) {
            var image = $scope.appData.appImages[i];
            active = i == 0 ? " active" : "";
            var item = "";
            item = "
+ active + "'> + $scope.selectedApp.name + "/" + image + "'>
"
; items += item; } $(document).ready(function () { $(".carousel-inner").html(items); }); }

This function iterates over all the preview image paths and creates a html string for the image carousel. Finally it inserts the html into the corresponding div.

The next three functions loads the text content from the respective file paths via ajax calls.
Let us see one of them as the other two are almost similar.

$scope.getStarted = function () {
        $http.get($scope.appName + "/" +$scope.appData.getStarted).success(function (data) {
            $(document).ready(function () {
                $(".get-started-md").html(converter.makeHtml(data));
            });
        });
}

Since this text contents will be used for showcasing purpose, raw text would look somewhat dull and boring. Developer’s should be able to customise them. But gain, we cannot go for css styling as the content is dynamically loaded, a single page is being used for all apps. What if developer is able to use markdown in their text? This will definitely solve the problem as developers will be able to format and style their content. So how do we render markdown to html? Well showdown.js allows us to do so. It dynamically renders markdown to html. This library has been used here to convert the markdown to html and finally insert it into the page using jquery.

After these three sections, there is a section showing some additional data like app version, last updated, contributors list, technology used, license, author’s website and mail etc. All these informations are fetched from app.json and displayed via AngularJS variables. The functional store listing page can be seen here

Important resources

Continue ReadingCreating Middle Section of Listing Page for loklak Apps Store

CSS Trick: How to Object-fit an Image inside its Container Element in the Open Event Front-end

I came across this piece of css when the Nextgen Conference Logo on eventyay home-page had its aspect ratio not maintained. As you can see in this picture that the image is stretched to fill in the parent container’s size.

The CSS behind this image was:

.event-holder img {
    width: 100%;
    height: 165px;
    border: none;
}

Let’s see how object fit helped me to fix this problem.

What is object-fit ?

The object-fit property of an element describes how it is fitted or placed inside its container element. This container box has its boundaries defined by the max-height and max-width attribute of the object in question.
In the html code for the above logo we had:

img {
    width: 100%;
    height: 165px; 
}

The object in this example is an image ( img ) which is to be fitted inside a box of height 165px with 100% width.

The object-fit property can refer to any element like video or embedded item in the page, but it’s mostly applied to images.

object-fit provides us with fine grained control over how the object resizes to fill inside its container div. Essentially object-fit lets the image ( in this context, but can be applied to any object ) fill the box withmaintaining aspect ratio and/or filling up the entire area established by height and weight.

Here’s a short example for different values of this attribute:
( The image
 used here is a 4096px*2660px image, placed inside a div of height 100px and width 300px. )

 
  object-fit: fill
   src="download.png" class="fill"/>
  object-fit: contain
   src="download.png" class="contain"/>
  object-fit: cover
   src="download.png" class="cover"/>
  object-fit: none
   src="download.png" class="none"/>
  object-fit: scale-down
   src="download.png" class="scale-down"/>

img {
  width: 300px;
  height: 100px;
  border: 1px solid yellow;
  background: blue;
}
.fill {
  object-fit: fill;
}
.contain {
  object-fit: contain;
}
.cover {
  object-fit: cover;
}
.none {
  object-fit: none;
}
.scale-down {
  object-fit: scale-down;
}

As from the above illustration, it is evident that what I needed to fix aspect ratio on home page was to use object-fit: cover. We got this result by just adding one line of code. Here’s the final CSS:

.event-holder img {
    width: 100%;
    height: 165px;
    object-fit: cover;
    border: none;
}

 

And the final image, which is pleasing and aesthetic:


Quick cheat-sheet for object-fit values

fill

  • stretches the image to fit the content box
  • aspect-ratio disregarded

contain

  • increases or decreases the size of the image to fill the box
  • aspect-ratio preserved

cover

  • fill the height and width of box
  • aspect ratio preserved
  • often the image gets cropped

none

  • height and width of the container box ignored
  • image retains its original size

scale-down

  • image takes smallest concrete object size between none and contain

Additional Resources

Continue ReadingCSS Trick: How to Object-fit an Image inside its Container Element in the Open Event Front-end

Creating a Responsive Menu in Open Event Frontend

Open Event Frontend uses semantic ui for creating responsive HTML components, however there are some components that are not responsive in nature like buttons & menus. Therefore we need to convert tabbed menus to a one column dropdown menu in mobile views. In this post I describe how we make menus responsive. We are creating a semantic UI custom styling component in Ember to achieve this.

In Open Event we are using the tabbed menus for navigation to a particular route as shown below.

Menu (Desktop)

As you can see there is an issue when viewing the menu on mobile screens.

Menu (Mobile)

Creating custom component for menu

To make the menu responsive we created a custom component called tabbed-navigation which converts the horizontal menu into a vertical dropdown menu for smaller screens. We are using semantic ui styling components for the component to implement the vertical dropdown for mobile view.

tabbed-navigation.js

currentRoute: computed('session.currentRouteName', 'item', function() {
  var path = this.get('session.currentRouteName');
  var item = this.get('item');
  if (path && item) {
    this.set('item', this.$('a.active'));
    this.$('a').addClass('vertical-item');
    return this.$('a.active').text().trim();
  }
}),
didInsertElement() {
  var isMobile = this.get('device.isMobile');
  if (isMobile) {
    this.$('a').addClass('vertical-item');
  }
  this.set('item', this.$('a.active'));
},
actions: {
  toggleMenu() {
    var menu = this.$('div.menu');
    menu.toggleClass('hidden');
  }
}

In the component we check if the device is mobile & change the classes accordingly. For mobile devices we add the vertical-item class to all the items in the menu. We set a property called item in the component which stores the selected item of the menu.

We add a computed property called currentRoute which observes the current route and the selected item, and sets the item to currently active route, and returns the name of the current route.

We add an action toggleMenu which is used to toggle the display of the vertical menu for mobile devices.

tabbed-navigation.hbs

We add a vertical menu dynamically for mobile devices with a button and the name of the current selected item which is stored in currentRoute variable, we also toggle between horizontal & vertical menu based on the screen size.

{{#if device.isMobile}}
  <div role="button" class="ui segment center aligned" {{action 'toggleMenu'}}>
    {{currentRoute}}
  </div>
{{/if}}
<div role="button" class="mobile hidden ui fluid stackable {{unless isNonPointing (unless device.isMobile 'pointing')}} {{unless device.isMobile (if isTabbed 'tabular' (if isVertical 'vertical' 'secondary'))}} menu" {{action 'toggleMenu'}}>
  {{yield}}
</div>

tabbed-navigation.scss

.tabbed-navigation {
  .vertical-item {
    display: block !important;
    text-align: center;
  }
}

Our custom component must look like an item of the menu, to ensure this we use display block property which will allow us to place the menu appear below the toggle button. We also center the menu items so that it looks more like a vertical dropdown.

{{#tabbed-navigation}}
  {{#link-to 'events.view.index' class='item'}}
    {{t 'Overview'}}
  {{/link-to}}
  {{#link-to 'events.view.tickets' class='item'}}
    {{t 'Tickets'}}
  {{/link-to}}
  <a href="#" class='item'>{{t 'Scheduler'}}</a>
  {{#link-to 'events.view.sessions' class='item'}}
    {{t 'Sessions'}}
  {{/link-to}}
  {{#link-to 'events.view.speakers' class='item'}}
    {{t 'Speakers'}}
  {{/link-to}}
  {{#link-to 'events.view.export' class='item'}}
    {{t 'Export'}}
  {{/link-to}}
{{/tabbed-navigation}}

To use this component all we need to do is wrap our menu inside the tabbed-navigation component and it will convert the horizontal menu to the vertical menu for mobile devices.

The outcome of this change on the Open Event Front-end now looks like this:

Thank you for reading the blog, you can check the source code for the example here.

Resources

Continue ReadingCreating a Responsive Menu in Open Event Frontend

Creating a store listing page for Loklak Apps Store

The various apps built with the loklak api is presently hosted at apps.loklak.org. On visiting the site, the users are presented with an app wall from where users can select any app and the user will be taken to the live version of the app. However there is no store listing page available like other app sites (for ex. Google playstore) where information about the app like how to use the app, what the app does, getting started with the app can be found.

The first part of my GSOC project is to create such an app store listing page where users/developers can find  all relevant information about the selected app.

Proposed design of the store listing page

The store listing page will be designed in three column format. There will be a left sidebar which will contain a list of categories for navigation, the category to which the selected app belongs will be highlighted. There will be a right side bar which will contain a list of similar apps for suggestion. The middle section will contain all the necessary information about the selected application, promo image and app preview images. Apart from this the page will contain a navbar for basic navigation.

Getting started with the store listing page implementation

Let’s get started with the left sidebar. Target is to make the entire page as dynamic as possible. Minimum information will be hard coded in html  and js code. Maximum data will be fetched from external json files using ajax requests. In this way if we need to make any change to data, it can be done by simply changing the data in the json files, the html and js code can be left intact.

First the store listing page has to know which app has been selected by the user. For this the app name is sent as an url parameter to the store listing page. The store listing page itself will have url like this http://apps.loklak.org/details.html?q=<app_name>. From here we can easily get the app name as shown below.

var addr = window.location + "";
if (addr.indexOf("?") !== -1) {
    $scope.appName = addr.split("?")[1].split("=")[1];
}

The category names, icon, and color are loaded from apps.json file. The apps.json file is one of the most important files in the repository which manages app and site meta data. It contains list of category objects each of which contains category name, category style and category icon reference.

$http.get('apps.json').success(function (data) {
        $scope.categories = data.categories;
        $scope.apps = data.apps;
        $scope.categories.unshift({"name": "All","image":"all.png","style" :
            {"background-color": "#ED3B3B"}});
        $scope.getSelectedApp();
        $scope.getSimilarApps();
    });

Using the above code we get all the categories and apps and bind them with the scope variable so that we can access them from the corresponding HTML.

Next we need to actually setup the UI for the sidebar using the data we have acquired. Well here comes in AngularJs’s one of the most useful feature – two way data binding. Using two way data binding we can easily access the category list from HTML code, iterate over it and set up the UI.

<div class="category-body">
              <ul class="menu-list">
                <li class="menu-list-item" ng-repeat="category in categories track by $index">
                  <a href="/#{{category.name.split(' ').join('')}}" class="menu-item" id="{{category.name | nospace}}"
                    ng-class="{selected: category.name.split(' ').join('') ==
                      selectedApp.applicationCategory.split(' ').join('')}">
                    <span class="category-image" ng-style = {{category.style}}>
                      <img ng-src="images/sidebar-images/{{category.image}}">
                    </span>
                    <span class="category-name"> {{category.name}} </span>
                  </a>
                </li>
              </ul>
            </div>

As it can be seen from the above code ng-class is being used to conditionally add a class named ‘selected’ for highlighting the category to which the selected app belongs. Each icon is given its own custom background color using ng-style, and the source is set using ng-src. Clicking on an category will take the user to the main page (app wall) and the user will be presented with only those apps which belong to the category which the user previously selected.

Creating a suggestion list

Next step is creating the right side column. It will contain all the other apps which are similar to the one selected by the user. For this we need to filter out all the apps which belongs to the same category to which the user selected app belongs. This can be done easily by using the list of apps which we have got from apps.json file. We can simply iterate over all the apps and filter the apps from the required category into another list.

$scope.getSimilarApps = function() {
        $scope.apps.forEach(function (item) {
            if (item.applicationCategory === $scope.selectedApp.applicationCategory
                && item.name !== $scope.selectedApp.name) {
                $scope.similarApps.push(item);
            }
        });
    }

This small function does the task of filtering and populates the similarApps list.

Next, once again we need to setup the UI using the data AngularJs acquired. For this we again iterate over the similarApps list and this time we set up cards to hold the app image and name.

<div class="similar-apps">
            <div class="similar-apps-header">
              <h4> Similar apps </h4>
            </div>
            <div class="similar-apps-list">
              <div ng-repeat="app in similarApps" class="card fadeIn">
                <a href="../details.html?q={{app.name}}">
                  <img ng-src="../{{app.name}}/screenshot.png" class="similar-app-image" alt="App image">
                  <div class="card-container">
                    <h4>{{app.headline}}</h4>
                  </div>
                </a>
              </div>
            </div>
          </div>

On clicking on any of the card, the store listing page of the corresponding app will be shown.

Important resources

  •  Find more information of AngularJS and AngularJS services here.
Continue ReadingCreating a store listing page for Loklak Apps Store

Adding Stickers Feature on Phimpme Android App with ViewFlipper and Scrollview

For any image editing application stickers play an important role in editing an image. Phimpme is no different. There can be many different kinds of stickers. As Phimpme is an Open Source application. The user can add their own stickers to be implemented on Phimpme. But this gives a long and non-exhaustive list of stickers that the user has to go through. Sorting the stickers into different categories is one way of doing it. It provides quick access to the desired sticker a user wants to add to her or his photo. In order to achieve this, I am using ViewFlipper and Scrollview.

Categorizing the types of Stickers.

How it is done in Phimpme?

Currently, there are six different categories for stickers. Namely: Facial, Hearts, Objects, Comments, Wishes, Animals. The following code is used to store the category name:

public static final String[] stickerPath = {"stickers/type1", "stickers/type2", "stickers/type3", "stickers/type4", "stickers/type5", "stickers/type6"};
public static final String[] stickerPathName = {"Facial", "Hearts", "Objects", "Comments", "Wishes", "Animals"};

 

We need to populate the sticker fragment with the different categories. This is done by overriding onBinderHolder() function and using ImageHolder to handle onClickListener.

private ImageClick mImageClick = new ImageClick();

Getting the position of the stickerPathName and setting it in stickerFragment.

@Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        ImageHolder imageHoler = (ImageHolder) holder;
        String name = stickerPathName[position];
        imageHoler.text.setText(name);
        imageHoler.text.setTag(stickerPath[position]);
        imageHoler.text.setOnClickListener(mImageClick);
    }

 

Adding Stickers to the different categories.

Now that we have different categories, we can assign different types of stickers to specific categories.

We are using RecyclerView.Adapter<ViewHolder>, which implements onCreateViewHolder() method. onCreateViewHolder method is created every time we need a new view, hence RecyclerView comes handy in such situation rather than a normal list.

First, we need to define the xml file where each sticker will be displayed in the form of ImageView. This ImageView has specific maxHeight and maxWidth for a uniform view.

The xml file name is: view_sticker_item.xml

<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/img"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:maxHeight="50dp"
    android:maxWidth="50dp"
    android:paddingLeft="5dp"
    android:paddingRight="5dp"
    android:scaleType="centerInside" />


After we set up view_sticker_item.xml, we have to inflate it.

@Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewtype) {
        View v = null;
        v = LayoutInflater.from(parent.getContext()).inflate(
                R.layout.view_sticker_item, parent, false);
        ImageHolder holder = new ImageHolder(v);
        return holder;
    }

 

I have saved all the images in the assets folder and distributed it among type1, type2, type3, type4, type5 and type6.

One thing is to be kept in mind that the images should be in png format. PNG format images are only pixelated on the required areas. So that it does not hamper the background of the image.

Displaying stickers

Displaying sticker are done by overriding onBindViewHolder again. It accepts two parameters: ViewHolder holder and int position. ImageHolder is used to set the image of the sticker and then handling it by onClickListner().

@Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        ImageHolder imageHoler = (ImageHolder) holder;
        String path = pathList.get(position);
        ImageLoader.getInstance().displayImage("assets://" + path,
                imageHoler.image, imageOption);
        imageHoler.image.setTag(path);
        imageHoler.image.setOnClickListener(mImageClick);
    }

 

public void addStickerImages(String folderPath) {
        pathList.clear();
        try {
            String[] files = mStirckerFragment.getActivity().getAssets()
                    .list(folderPath);
            for (String name : files) {
                pathList.add(folderPath + File.separator + name);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        this.notifyDataSetChanged();
    }

 

So sticker fragment on Phimpme looks like this:  

                                        

Link:

https://github.com/fossasia/phimpme-android

Resources

 

Continue ReadingAdding Stickers Feature on Phimpme Android App with ViewFlipper and Scrollview

Handling soft and hard deletes in the Open Event server API

Really, handling soft and hard deletes can be a mess, if you think of it.

Earlier in the Open Event server project, we had a Boolean field called is_trashed which was set to true if a record was soft-deleted. That worked just fine, until there came a requirement to get the time at which the record was deleted. So duh… we added another column called deleted_at which would store the time at which the record was soft-deleted. And it all started working fine again.

But, shortly we realised it was bad design to have a redundant Boolean field is_trashed. So it was decided to remove the is_trashed field and only keep the deleted_at column at all places. If the deleted_at field contained a date, it would mean that the record has been soft deleted at that point of time. If the field was still NULL, then the record has not been soft deleted. That ends up the database aspect of implementing soft-deletes. Let’s move on to the API part then.

We are currently in the process of decoupling our front-end and back-end. And the API server for the same is in active development. We’ve been using flask-rest-jsonapi for the same purpose. So, the first thing that popped up in our minds, when we got around handling soft-deletes was the following.

Should the API framework implement soft-deletes for each API by itself, or should the individual API logic take care of it ?

After some discussion, it was decided to let the framework handle it for each API, so that the implementation remains uniform and obviously a little less headache for the developers. In our custom copy of flask-rest-jsonapi, we also added an option to turn off the soft deletes across the whole API. Turning it off for each resource is also in our road map and would be soon implemented in the future.

Now talking about the API itself, for GET endpoints by default soft-deleted records should not be retrieved. Retrieving all the records irrespective of whether it is soft-deleted or not and letting client figure out which records are deleted is a sign of bad design. If the client wants to retrieve the deleted records, it can do so by passing a query parameter is_trashed set to true.

Following is the URL pattern followed for the same, for the sake of the example, assume that the event with id 1 is soft-deleted:

GET /events?with_trashed=true   # get all events including the soft-deleted events
GET /events/1                       # send a 404 exception
GET /events/1?with_trashed=true # retrieve relevant data 

For DELETE request:

DELETE /events/1                 # soft-delete the event
DELETE /events/1?permanent=true   # hard-delete the event

Relevant links:

Continue ReadingHandling soft and hard deletes in the Open Event server API

How to add a custom filter to pypandoc

In Yaydoc, we met the problem of converting Markdown file into restructuredText because sphinx needs restructured text.  

Let us say we have a yml CodeBlock in Yaydoc’s README.md, but sphinx  uses pygments for code highlighting which needs yaml instead of yml for proper documentation generation. Pandoc has an excellent feature which allows us to write our own custom logic to the AST parser.

INPUT --reader--> AST --filter--> AST --writer--> OUTPUT

Let me explain this in a few steps:

  1. Initially pandoc reads the file and then converts it into nodes.
  2. Then the nodes is sent to Pandoc AST for parsing the markdown to restructuredText.
  3. The parsed node will then go to the filter. The filter is converting the parsed node according to the logic implemented.
  4. Then the Pandoc AST performs further parsing and joins the Nodes into text and is written to the file.

One important point to remember is that, Pandoc reads the conversion from the filter output stream so don’t write print statement in the filter. If you write print statement pandoc cannot  parse the JSON. In order to do debugging you can use logging module from python standard module. Here is the sample Pypandoc filter:

#!/usr/bin/env python
from pandocfilters import CodeBlock, toJSONFilter


def yml_to_yaml(key, val, fmt, meta):
    if key == 'CodeBlock':
        [[indent, classes, keyvals], code] = val
        if len(classes) > 0:
            if classes[0] == u'yml':
                classes[0] = u'yaml'
        val = [[indent, classes, keyvals], code]
        return CodeBlock(val[0], val[1])


if __name__ == "__main__":
    toJSONFilter(yml_to_yaml)

The above snippet checks whether the node is a CodeBlock or not. If it is a CodeBlock, it changes yml to yaml and prints it as a JSON in the output stream. It is then parsed by pandoc.

Finally, all you have to do is to add your filter to the Pypandoc’s filters argument.

output = pypandoc.convert_text(text, 'rst', format='md', filters=[os.path.join('filters', 'yml_filter.py')])

Resources:
http://pandoc.org/scripting.html#json-filters

Continue ReadingHow to add a custom filter to pypandoc

Integrating Selenium Testing in the Open Event Webapp

Enter Selenium. Selenium is a suite of tools to automate web browsers across many platforms. Using Selenium, we can control the browser and instruct it to do an ‘action’ programmatically. We can then check whether that action had the appropriate reaction and make our test cases based on this concept. There are various implementations of Selenium available in many different languages: Java, Ruby, Javascript, Python etc. As the main language used in the project is Javascript, we decided to use it.

https://www.npmjs.com/package/selenium-webdriver
https://seleniumhq.github.io/selenium/docs/api/javascript/index.html

After deciding on the framework to be used in the project, we had to find a way to integrate it in the project. We wanted to run the tests on every PR made to the repo. If it failed, the build would be stopped and it would be shown to the user. Now, the problem was that the Travis doesn’t natively support running Selenium on their virtual machine. Fortunately, we have a company called Sauce Labs which provides automated testing for the web and mobile applications. And the best part, it is totally free for open source projects. And Travis supports Sauce Labs. The details of how to connect to Sauce Labs is described in detail on this page:

https://docs.travis-ci.com/user/gui-and-headless-browsers/

Basically, we have to create an account on Sauce Labs and get a sauce_username and sauce_access_key which will be used to connect to the sauce cloud. Travis provides a sauce_connect addon which creates a tunnel which allows the Sauce browsers to easily access our application. Once the tunnel is established, the browser in the Sauce cloud can use it to access the localhost where we serve the pages of the generated sites. A little code would make it more clear at this stage:

Here is a short excerpt from the travis.yml file :-

addons:
 sauce_connect:
   username: princu7
 jwt:
   secure: FslueGK2gtPHkRANMpUlGyCGsr1jTVuaKpP+SvYUxBYh5zbz73GMq+VsqlE29IZ1ER1+xMfWuCCvg3VA7HePyN6hzoZ/t0LADureYVPur6R5ZJgqgQpBinjpytIjo2BhN3NqaNWaIJZTLDSAT76R7HuNm01=

As we can see from the code, we have installed the sauce_connect addon and then added the sauce_username and sauce_access_key which we got when we registered on the cloud. Now, what is this gibberish we are seeing? Well, that is actually the sauce_access_key. It is just in its encrypted form. Generally, it is not a good practice to show the access keys in the source code. Anyone else can then use it and can cause harm to the resources allocated to us. You can read all about encrypting environment variables and JWT (JSON Web Tokens) here:-

https://docs.travis-ci.com/user/environment-variables/
https://docs.travis-ci.com/user/jwt

So, this sets up our tunnel to the Sauce Cloud. Here is one of the screenshots showing that our tunnel is opened and tests can be run through it.

sauce.png

After this, our next step is to make our test scripts run in the Sauce Cloud through the tunnel. We already use a testing framework mocha in the project. We can easily use mocha to run our client-side tests too. Here is a link to study it in a little more detail:

http://samsaccone.com/posts/testing-with-travis-and-sauce-labs.html

This is a short excerpt of the code from the test script

describe("Running Selenium tests on Chrome Driver", function() {
 this.timeout(600000);
 var driver;
 before(function() {
   if (process.env.SAUCE_USERNAME !== undefined) {
     driver = new webdriver.Builder()
       .usingServer('http://'+    process.env.SAUCE_USERNAME+':'+process.env.SAUCE_ACCESS_KEY+'@ondemand.saucelabs.com:80/wd/hub')
       .withCapabilities({
         'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
         build: process.env.TRAVIS_BUILD_NUMBER,
         username: process.env.SAUCE_USERNAME,
         accessKey: process.env.SAUCE_ACCESS_KEY,
         browserName: "chrome"
       }).build();
   } else {
     driver = new webdriver.Builder()
       .withCapabilities({
         browserName: "chrome"
       }).build();
   }
 });

 after(function() {
   return driver.quit();
 });

 describe('Testing event page', function() {

   before(function() {
     eventPage.init(driver);
           eventPage.visit('http://localhost:5000/live/preview/a@a.com/FOSSASIASummit');
   });

   it('Checking the title of the page', function(done) {
     eventPage.getEventName().then(function(eventName) {
       assert.equal(eventName, "FOSSASIA Summit");
       done();
     });
   });
 });
});

Without going too much into the detail, I would like to offer a brief overview of what is going on. At a high level, before starting any tests, we are checking whether the test is being run in a Travis environment. If yes, then we are appropriately setting up the webdriver for it to run on the Sauce Cloud through the tunnel which we opened previously. We also specify the browser on which we would like to run, which in this care here, is Chrome.

After this preliminary setup is done, we move on to the actual tests. Currently, we only have a basic test to check whether the title of the event site generated is correct or not. We had generated FOSSASIA Summit in the earlier part of the test script. So we just run the site and check its title which should obviously be ‘FOSSASIA Summit’. If due to some error it is not the case, then an error will be thrown and the Travis build we fail. Here is the screenshot of a successful passing test:

6c0a0775-6ad4-45f9-bfa1-a8baef4e6401.png

More tests will be added over the upcoming weeks.

Resources:

Continue ReadingIntegrating Selenium Testing in the Open Event Webapp

Adding Twitter Integration with MVP Architecture in Phimpme Android

The Account manager layout part of Phimpme is set. Now we need to start adding different account to make it functional. We first start with twitter. Twitter functionality is integrated with the help of Twitter Kit provided by Twitter itself. We followed the steps provided on the installation guide.

Note: Before Starting this first go to apps.twitter.com and create new app, add the relevant information such as name, description, URL etc.

How twitter works in Phimpme

A dialog box appear when user selected the add account option in Account manager. Select Twitter option from it.

Twitter guides to use custom TwitterLoginButton for sign in. But as we are using a common dialog box. How to initiate login from there?

Using TwitterAuthClient

Twitter Auth Client invoke the Twitter callback and popup the login window. On authorized the correct user it goes inside the onSuccess method and start a Twitter session which helps us to get the information which we want to store in database such as username, access token.

client.authorize(getActivity(), new Callback<TwitterSession>() {
   @Override
   public void success(Result<TwitterSession> result) {

       // Creating twitter session, after user authenticate
       // in twitter popup
       TwitterSession session = TwitterCore.getInstance()
               .getSessionManager().getActiveSession();
       TwitterAuthToken authToken = session.getAuthToken();


       // Writing values in Realm database
       account.setUsername(session.getUserName());
       account.setToken(String.valueOf(session.getAuthToken()));

   }

Working with MVP architecture to show Twitter data to User in a recyclerView

Finally after successful login from Twitter, we also need to show user that you are successfully logged in Phimpme app and also provide a sign out feature so that user can logout from Twitter anytime.

Account manager has a recyclerView which takes data from the database and show it to the User.

Steps:

class AccountContract {
   internal interface View : MvpView{

       /**
        * Setting up the recyclerView. The layout manager, decorator etc.
        */
       fun setUpRecyclerView()

       /**
        * Account Presenter calls this function after taking data from Database Helper Class
        */
       fun setUpAdapter(accountDetails: RealmResults<AccountDatabase>)

       /**
        * Shows the error log
        */
       fun showError()
   }

   internal interface Presenter {

       /**
        * function to load data from database, using Database Helper class
        */
       fun loadFromDatabase()

       /**
        * setting up the recyclerView adapter from here
        */
       fun handleResults(accountDetails: RealmResults<AccountDatabase>)
   }
}

This class clearly show what functions are in View and what are in Presenter. The View interface extended to MvpView which actually holds some common functions such as onComplete()

  • Implement View interface in AccountActivity

class AccountActivity : ThemedActivity(), AccountContract.View

And perform all the action which are happening on the View such as setting up the RecyclerView

override fun setUpRecyclerView() {
   val layoutManager = LinearLayoutManager(this)
   accountsRecyclerView!!.setLayoutManager(layoutManager)
   accountsRecyclerView!!.setAdapter(accountAdapter)
}
  • Main Business Logic should not be in Activity class

That’s why using MVP we have very less number of lines of code in our Main Activity because it separate the work on different zones which help developer to easily work, maintain and other user to contribute.

So like in our case I need to update the RecyclerView adapter by taking data from database. That work should not be in activity that’s why I create a class AccountPresenter and extend this to our Presenter interface in contract class

class AccountPresenter extends BasePresenter<AccountContract.View>
       implements AccountContract.Presenter

I added the function which take care of loading data from database

@Override
public void loadFromDatabase() {
   handleResults(databaseHelper.fetchAccountDetails());
}
  • Always consider the future and keep an eye for future development

Right now I not need to do alot on Database, I just need to pick the whole data and show it on View. But I need to take case of future development in this part as well. There might be more complex operation on Database in future, then it will create complexity in the codebase, if it is not architectured well.

So, I created a DatabaseHelper class which takes care of all the database operations, the advantage of this is, anyone who have to contribute in Database part or debugging the databse need not to search for every activity and scroll lines of code, the work will be in DatabaseHelper for sure.

Added DatabaseHelper in data package

public class DatabaseHelper {

   private Realm realm;

   public DatabaseHelper(Realm realm) {
       this.realm = realm;
   }

   public RealmResults<AccountDatabase> fetchAccountDetails(){
       return realm.where(AccountDatabase.class).findAll();
   }

   public void deleteSignedOutAccount(String accountName){
       final RealmResults<AccountDatabase> deletionQueryResult =  realm.where(AccountDatabase.class)
               .equalTo("name", accountName).findAll();

       realm.executeTransaction(new Realm.Transaction() {
           @Override
           public void execute(Realm realm) {
               deletionQueryResult.deleteAllFromRealm();
           }
       });
   }
}

Flow Diagram:

Browse the Phimpme GitHub Repository for complete illustration.

Resources

  1. Twitter KIt overview : https://dev.twitter.com/twitterkit/android/overview
  2. Login with Twitter: https://dev.twitter.com/twitterkit/android/log-in-with-twitter
  3. MVP Architecture by Ribot: https://github.com/ribot/android-boilerplate

 

Continue ReadingAdding Twitter Integration with MVP Architecture in Phimpme Android