diff --git a/lib/DDG/Spice/Pokemon/Data.pm b/lib/DDG/Spice/Pokemon/Data.pm new file mode 100644 index 0000000000..734316a79b --- /dev/null +++ b/lib/DDG/Spice/Pokemon/Data.pm @@ -0,0 +1,31 @@ +package DDG::Spice::Pokemon::Data; +# ABSTRACT: Returns pokemon data from the pokeapi API + +use DDG::Spice; + +spice is_cached => 1; +spice proxy_cache_valid => "200 30d"; + +name "Pokemon"; +source "Pokéapi"; +icon_url "/i/www.pokeapi.co.ico"; +description "Fetches details of a given Pokemon"; +primary_example_queries "pokemon pikachu", "bulbasaur pokemon"; +category "entertainment"; +topics "entertainment", "geek"; +code_url "https://github.com/duckduckgo/zeroclickinfo-spice/blob/master/lib/DDG/Spice/Pokemon.pm"; +attribution github => ["AkA84"]; + +# Triggers +triggers startend => "pokemon"; + +spice to => 'http://pokeapi.co/api/v1/pokemon/$1/'; +spice wrap_jsonp_callback => 1; + +# Handle statement +handle remainder => sub { + return lc $_ if $_; + return; +}; + +1; diff --git a/lib/DDG/Spice/Pokemon/Description.pm b/lib/DDG/Spice/Pokemon/Description.pm new file mode 100644 index 0000000000..c6e4b23616 --- /dev/null +++ b/lib/DDG/Spice/Pokemon/Description.pm @@ -0,0 +1,20 @@ +package DDG::Spice::Pokemon::Description; +# ABSTRACT: Returns pokemon description from the pokeapi API + +use DDG::Spice; + +spice is_cached => 1; +spice proxy_cache_valid => "200 30d"; + +attribution github => ["AkA84"]; + +triggers any => "///***never_trigger***///"; + +spice to => 'http://pokeapi.co/api/v1/description/$1/'; + +handle remainder => sub { + return $_ if $_; + return; +}; + +1; \ No newline at end of file diff --git a/share/spice/pokemon/data/content.handlebars b/share/spice/pokemon/data/content.handlebars new file mode 100644 index 0000000000..d51d8778b1 --- /dev/null +++ b/share/spice/pokemon/data/content.handlebars @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/share/spice/pokemon/data/pokemon_data.css b/share/spice/pokemon/data/pokemon_data.css new file mode 100644 index 0000000000..10a98b6ad1 --- /dev/null +++ b/share/spice/pokemon/data/pokemon_data.css @@ -0,0 +1,8 @@ +.zci--pokemon .pokemon__description { + margin-bottom: 10px; +} + +.zci--pokemon .zci__header__sub { + display: block; + padding-left: 0; +} \ No newline at end of file diff --git a/share/spice/pokemon/data/pokemon_data.js b/share/spice/pokemon/data/pokemon_data.js new file mode 100644 index 0000000000..1cc7665552 --- /dev/null +++ b/share/spice/pokemon/data/pokemon_data.js @@ -0,0 +1,136 @@ +(function (env) { + "use strict"; + + var ID = 'pokemon', + INFOBOX_PROPS = ['hp', 'attack', 'defense', 'height', 'weight', 'speed'], + DESCRIPTION_ENDPOINT = '/js/spice/pokemon/description/{id}', + POKEAPI_SPRITE_URL = 'http://pokeapi.co/media/img/{id}.png'; + /** + * [ Pokemon::Data ] + */ + env.ddg_spice_pokemon_data = function(api_result){ + if( !api_result ) { + return Spice.failed('pokemon'); + } + + Spice.add({ + id: ID, + name: 'Pokedex', + data: api_result, + meta: { + sourceName: 'pokeapi.co', + sourceUrl: 'http://pokeapi.co/', + sourceIconUrl: 'http://pokeapi.co/static/favicon.ico' + }, + normalize: function(item) { + return { + title: item.name, + image: getSprite.call(item), + imageIsLogo: true, + infoboxData: getInfoboxData.call(item), + subtitle: (function(evolutions) { + if( evolutions.length > 0 ) { + var html = 'Evolves into: {name}'; + + return new Handlebars.SafeString(html.replace(/{name}/g, evolutions[0].to)); + } + }(item.evolutions)) + }; + }, + onShow: function() { + fetchDescription(api_result.descriptions).done(function(api_result) { + var description = api_result ? api_result.description : 'Description not available'; + + Spice.getDOM(ID).find('.pokemon__description').html(description); + }); + }, + templates: { + group: 'info', + options: { + content: Spice.pokemon_data.content, + moreAt: true + } + } + }); + }; + + /** + * Chooses a random pokemon's description from the available sets + * and then calls the Pokemon::Description endpoint to fetch its full text + * + * @param {Array} descriptions + * @return {jqXHR} the Promise object + */ + function fetchDescription(descriptions) { + if( descriptions.length > 0 ) { + var randIndex = Math.floor(Math.random() * descriptions.length), + id = descriptions[randIndex].resource_uri.match(/(\d+)\/$/)[1]; + + return $.getJSON(DESCRIPTION_ENDPOINT.replace('{id}', id)); + } else { + return new jQuery.Deferred().resolve(null).promise(); + } + } + + /** + * Returns the list of capitalized names from a collection + * + * @context {item} + * @param {Array} collection + * @return {Array} + */ + function getCollectionNames(collection) { + return $.map(this[collection], function(element) { + return DDG.capitalize(element.name); + }); + } + + /** + * Returns the list of label/value pairs to display in the Info Box + * + * @context {item} + * @return {Array} + */ + function getInfoboxData() { + var infoboxData = [{ heading: 'Info' }]; + + if( this.types.length > 0 ) { + infoboxData.push({ + label: 'Type', + value: getCollectionNames.call(this, 'types').join(', ') + }); + } + + if( this.egg_groups.length > 0 ) { + infoboxData.push({ + label: 'Egg groups', + value: getCollectionNames.call(this, 'egg_groups').join(', ') + }); + } + + for( var prop in this ) { + if( INFOBOX_PROPS.indexOf(prop) !== -1 && parseInt(this[prop], 10) ) { + infoboxData.push({ + label: DDG.capitalize(prop), + value: this[prop] + }); + } + } + + return infoboxData; + } + + /** + * Returns the url of the pokemon's sprite or null if the API doesn't provide any + * + * @context {item} + * @return {String / null} + */ + function getSprite() { + if( this.sprites.length !== 0 ) { + return POKEAPI_SPRITE_URL.replace('{id}', this.national_id); + } else { + return null; + } + } +}(this)); diff --git a/t/Pokemon.t b/t/Pokemon.t new file mode 100644 index 0000000000..5cb1f7bc92 --- /dev/null +++ b/t/Pokemon.t @@ -0,0 +1,26 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use Test::More; +use DDG::Test::Spice; + +spice is_cached => 1; + +ddg_spice_test( + [qw( DDG::Spice::Pokemon::Data)], + 'pikachu pokemon' => test_spice( + '/js/spice/pokemon/data/pikachu', + call_type => 'include', + caller => 'DDG::Spice::Pokemon::Data' + ), + 'pokemon Charizard' => test_spice( + '/js/spice/pokemon/data/charizard', + call_type => 'include', + caller => 'DDG::Spice::Pokemon::Data' + ), + 'bulbasaur pokemon stats' => undef, +); + +done_testing; +