Skip to content
This repository

DuckDuckGo instant answer plugins based on JavaScript APIs

tag: 0.140

Fetching latest commit…

Cannot retrieve the latest commit at this time

README.md

DuckDuckHack Spice

This documentation walks you through the process of writing a DuckDuckHack Spice plugin. Before reading this section, make sure you've read the DuckDuckHack Intro Site and the DuckDuckHack Developer's Overview (so you know what DuckDuckHack is) and have worked through the Basic tutorial.

Spice Handle Functions

Spice plugins have triggers and handle functions like Goodies, as explained in the Basic tutorial. The difference is that Spice handle functions don't return an instant answer directly like Goodies. Instead, they return arguments used to call a JavaScript callback function that then returns the instant answer.

The JavaScript callback function is defined in another file and is explained in detail in the Spice callback functions section. For now let's concentrate on how it gets called via the Spice handle function.

Usually the Spice plugin flow works like this:

  • Spice plugin is triggered.
  • Spice handle function is called.
  • Spice handle function returns arguments.
  • Arguments are used to make a call to an external JSONP API.
  • The external API returns a JSON object to the Spice callback function.
  • Spice callback function returns instant answer.
  • Instant answer formatted on screen.

The following is an example that calls the Twitter API. Within your zeroclickinfo-spice fork, you would define a similar file in the /lib/DDG/Spice/ directory. This file is named Twitter.pm.

package DDG::Spice::Twitter;

use DDG::Spice;

spice to => 'http://twitter.com/status/user_timeline/$1.json?callback={{callback}}';

triggers query_lc => qr/^@([^\s]+)$/;

handle matches => sub {
    my ($uname) = @_;
    return $uname if $uname;
    return;
};

1;

To refresh your memory, the triggers keyword tells the plugin system when to call a plugin. In the Basic tutorial we discussed using the start keyword to specify trigger words that need to be present at the beginning of the query.

In situations where you want to trigger on sub-words, you can pass a regular expression like in this Twitter example.

triggers query_lc => qr/^@([^\s]+)$/;

The query_lc keyword tells the trigger system to examine a lower case version of the query. The qr/regexp/ construct is the way to specify a compiled regular expression in Perl.

In this case ^@([^\s]+)$ says look for a @ character at the beginning of the query (the ^) and capture (using the parenthesis) everything that isn't a space ( [^\s] ) until you get to the end of the query (the $). Therefore it will match a query like @duckduckgo and capture the duckduckgo part.

The captured parts (matches) get passed to the handle function via the @_ variable (a special Perl array variable).

handle matches => sub {
    my ($uname) = @_;
    return $uname if $uname;
    return;
};

Previously we saw the use of the remainder keyword as in handle remainder, which works well for trigger words. In a case like this one that uses a regular expression trigger, the equivalent is handle matches, which passes the captured parts of the regular expression to the handle function. We look at what was passed and put it into the $uname variable.

    my ($uname) = @_;

If we received a non-blank user name then we return it.

    return $uname if $uname;

Otherwise, return nothing, which short circuits the eventual external call.

   return;

When the username is returned we then plug it into the spice to definition.

spice to => 'http://twitter.com/status/user_timeline/$1.json?callback={{callback}}';

The $uname value from the return statement will get inserted into the $1 placeholder in the spice to line such that you can plug in parameters to the API call as needed. For passing multiple parameters, check out the Advanced spice handlers section.

The {{callback}} template gets plugged in automatically with the default callback value of ddg_spice_twitter. That last part (twitter) is a lowercase version of the plugin name with different words separated by the _ character.

At this point the response moves from the backend to the frontend. The external API sends a JSON object to the callback function that you will also define (as explained in the Spice callback functions section).

Where to go now:

Before moving on to the section below, you should make sure your trigger is doing what you think it is by following the Testing triggers section.

Spice Callback Functions

Before reading this section, make sure you've read the basic tutorial, the section on spice handle functions, and the section on testing triggers.

As explained in the Spice handle functions section, a Spice plugin usually calls an external API and returns a JSON object to a callback function. This section explains what that callback function looks like.

Please note: the interface of the callback function is the most beta part of the Spice system, and will be changing soon (for the better). However, you can work away without worrying about what any changes might do to your plugins -- we'll take care of all that.

The callback function is named ddg_spice_plugin_name where plugin_name becomes the name of your plugin. For example, for the Twitter plugin the callback name is ddg_spice_twitter. For multiple word names the CamelCase in the plugin name becomes lower case and separated by _, e.g. HackerNews becomes hacker_news.

Whereas the Spice handle function went in the /lib/DDG/Spice/ directory, the callback function goes in the /share/spice/plugin_name directory. You will need to make that directory. The callback function then gets placed inside a file called spice.js.

Here's a very simple callback function used in the Expatistan Spice at /share/spice/expatistan/spice.js:

function ddg_spice_expatistan(ir) {
    var snippet = '';
    if (ir['status'] == 'OK') {
       snippet = ir['abstract'];
       items = new Array();
       items[0] = new Array();
       items[0]['a'] = snippet;
       items[0]['h'] = '';
       items[0]['s'] = 'Expatistan';
       items[0]['u'] = ir['source_url'];
       nra(items);
    }
}

The end result is a call to the nra function, an internal display function that takes what you send it and formats it for instant answer display.

       nra(items);

We're sending it a JavaScript Array we created called items.

       items = new Array();

The first item in the Array is the main answer. It is another JavaScript Array.

       items[0] = new Array();

An item takes the following parameters.

items[0]['a'] = snippet;

The a param is the required answer. It can be pure HTML in which case it is set via innerHTML. It can also be an object (preferred), in which case onclick and other event handlers won't be destroyed.

The h param is an optional relevant (and relatively short) title.

items[0]['h'] = title;

Source name and URL are required in the s and u blocks. These are used to make the More at X link in all instant answer boxes. Think of it as source attribution.

items[0]['s'] = 'XKCD';
items[0]['u'] = url

An optional image can be passed in the i param. If there is a thumbnail image, we will display it on the right.

items[0]['i'] = image_url

You would usually get the information to make these assignments via the object returned to the callback function. In this case we received it in the ir variable but you can name it anything.

function ddg_spice_expatistan(ir) {

Next:

Something went wrong with that request. Please try again.