Coding Fun – So. Many. Images

For my fifth installment, we will use the Imgur API to search for random animated gifs, useful right?  Who says programming has to always be useful, distractions can be good for you.

Review the API documentation I linked above.  One thing you need to know about this API, you do have to register an account to use it, which has a quota.  Once you reach that quota I’d presume things stop working (I’ve never hit it).  You will need your API key to use this tutorial.

We’re going to create a new Imgur module, to handle all our Imgur logic which at this point is searching the gallery for images.

Here’s the framework for the module

const rp = require('request');

var IMGURAPIKEY = "<YOUR KEY>";

var search = (searchString, callback) => {
 var imgurl = 'https://api.imgur.com/3/gallery/search/';

 var options = {
  url: imgurl + 'viral?q_type=anigif&q_any=' + \
      encodeURIComponent(searchString),
  headers: {
   'Authorization': 'Client-ID ' + IMGURAPIKEY
  },
  json: true
 };

 rp.get(options, (error, response, data) => {
  if(!error && response.statusCode == 200) {
   for(var q in data.data) {
    if(!data.data[q].nsfw) {
     callback(data.data[q].link);
     break;
    }
   }
  }
 });
};

module.exports = {
  search
};

There isn’t much new here.  We load the request module, build our call to the API, and execute it.

One thing that probably warrants some discussion is the following

if(!error && response.statusCode == 200) {
 for(var q in data.data) {
  if(!data.data[q].nsfw) {
   callback(data.data[q].link);
   break;
  }
 }
}

Let’s break it down line by line

if(!error && response.statusCode == 200) {

In an HTTP request, two things can happen that we don’t want.

First would be an error, this is a true error, you couldn’t reach the server for some reason (DNS issue, routing problem, the server is offline, etc…).  If that happens the request module will set error to true, so we check to ensure it’s not (!) true.

Second would be that the server returns something other than HTTP status code 200.  Anything else would mean we got no data back, so there’s no point proceeding.  Ideally you want to add error handling and return something to the user.  My users know that when the bot never replies, it either had a failure or didn’t find anything.

for(var q in data.data) {
 ..
}

Imgur returns an array of data.  I obviously don’t want a chatbot flooding the room with data, so I’m going to loop over this array looking for the first image that meets qualifications.  This is setting up that loop.

if(!data.data[q].nsfw) {
 ..
}

I don’t want NSFW data, this is a work environment the bot is pasting to. Skip this if you don’t care about such things.  Notice that the variable q I set earlier is actually an array counter.  Python handles this differently which I might add as a bonus post, the differences in logic.  Probably should have used a better variable name than data earlier (since now we have the ultra confusing data.data) but I warned you that I’m not creative.

callback(data.data[q].link);
 break;

Remember my callback?  If you don’t know by now NodeJS uses these A LOT!  They are useful, and when you learn the recursion involved they start to make sense.  Here I’m sending the first link to an image through the callback.  I then break the for loop.  Again I don’t want to loop and flood my channel/users with a bunch of images, just one.

And that’s it, our main code can be adjusted to add this new search feature, here it is for your viewing pleasure.  I have added some logic to parse out the message content, nothing is new here so I won’t review it.

//Standard NPM modules
const express = require('express');
const bodyparser = require('body-parser');

const spark = require('./spark.js');
const imgur = require('./imgur.js');

var app = express();
app.use(bodyparser.json());
app.use(bodyparser.urlencoded({ extended: true }));

var botName = 'ImgurBot';

app.all('/spark', (req, res) => {
 res.send();
 spark.getMessage(req.body.data.id, (messageDetail) => {
  console.log('message:', messageDetail);
    if(messageDetail.roomType === 'group') {
      // Check if the bot is mentioned, if not ignore this
      if(messageDetail.text.split(' ')[0] === botName) {
        // Strip off the @ reference to the Bot
        imgurSearch = messageDetail.text.split(' ').slice(1).join(' ');
        imgur.search(imgurSearch, (link) => {
          console.log(link);
        });
      }
    } else {
      imgur.search(messageDetail.text, (link) => {
        console.log(link);
      });
    }
 });
});

app.listen(90);

One last thing to mention.  Within the imgur script I called this method

encodeURIComponent(searchString),

This is pretty critical.  I am encoding the search string to standard HTML characters, this should prevent any kind of exploit against the search.  Sanitizing things is important in web-security.

In reality this entire script needs to be hardened against attacks, again maybe in a future post I’ll do it and detail the changes.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s