diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4c0b7722..e37b4129 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -34,5 +34,6 @@ plugins/WorldCup2026/* @TimWheeler-SQUP plugins/Notion/* @clarkd plugins/Algolia/* @andrewmumblebee plugins/Vercel/* @andrewmumblebee +plugins/PokeAPI/* @clarkd diff --git a/plugins/PokeAPI/v1/configValidation.json b/plugins/PokeAPI/v1/configValidation.json new file mode 100644 index 00000000..24df4938 --- /dev/null +++ b/plugins/PokeAPI/v1/configValidation.json @@ -0,0 +1,11 @@ +{ + "steps": [ + { + "displayName": "Connect to PokéAPI", + "dataStream": { "name": "types" }, + "required": true, + "error": "Could not reach PokéAPI (https://pokeapi.co). Check your network connection or agent egress.", + "success": "Connected to PokéAPI successfully." + } + ] +} diff --git a/plugins/PokeAPI/v1/cspell.json b/plugins/PokeAPI/v1/cspell.json new file mode 100644 index 00000000..878cb8f6 --- /dev/null +++ b/plugins/PokeAPI/v1/cspell.json @@ -0,0 +1,24 @@ +{ + "words": [ + "Arceus", + "Charizard", + "clarkd", + "colour", + "Eevee", + "Farfetchd", + "Gigantamax", + "Heartgold", + "learnset", + "learnsets", + "localised", + "matchups", + "pokeapi", + "pokedex", + "Poké", + "Pokédex", + "Ponyta", + "Soulsilver", + "Tera", + "visualisation" + ] +} diff --git a/plugins/PokeAPI/v1/custom_types.json b/plugins/PokeAPI/v1/custom_types.json new file mode 100644 index 00000000..05c541ad --- /dev/null +++ b/plugins/PokeAPI/v1/custom_types.json @@ -0,0 +1,23 @@ +[ + { + "name": "Pokémon", + "sourceType": "Pokemon", + "icon": "paw", + "singular": "Pokémon", + "plural": "Pokémon" + }, + { + "name": "Pokémon Type", + "sourceType": "Pokemon Type", + "icon": "fire", + "singular": "Pokémon Type", + "plural": "Pokémon Types" + }, + { + "name": "Pokémon Generation", + "sourceType": "Pokemon Generation", + "icon": "gamepad", + "singular": "Generation", + "plural": "Generations" + } +] diff --git a/plugins/PokeAPI/v1/dataStreams/generationSummary.json b/plugins/PokeAPI/v1/dataStreams/generationSummary.json new file mode 100644 index 00000000..68ec1b9a --- /dev/null +++ b/plugins/PokeAPI/v1/dataStreams/generationSummary.json @@ -0,0 +1,31 @@ +{ + "name": "generationSummary", + "displayName": "Generation Summary", + "description": "Main region, games and the number of species, moves, abilities and types a generation introduced", + "tags": ["Generation"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/generation/{{object.rawId}}", + "postRequestScript": "generationSummary.js" + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Pokemon Generation"] } }, + "timeframes": false, + "metadata": [ + { "name": "generationId", "displayName": "Generation ID", "shape": "string", "visible": false }, + { + "name": "generationName", + "displayName": "Generation", + "shape": "string", + "role": "label" + }, + { "name": "region", "displayName": "Region", "shape": "string" }, + { "name": "games", "displayName": "Games", "shape": "string" }, + { "name": "newSpecies", "displayName": "New Species", "shape": "number", "role": "value" }, + { "name": "newMoves", "displayName": "New Moves", "shape": "number" }, + { "name": "newAbilities", "displayName": "New Abilities", "shape": "number" }, + { "name": "newTypes", "displayName": "New Types", "shape": "number" }, + { "name": "typesIntroduced", "displayName": "Types Introduced", "shape": "string" }, + { "sourceId": "generationId", "sourceType": "Pokemon Generation", "name": "generationName" } + ] +} diff --git a/plugins/PokeAPI/v1/dataStreams/generations.json b/plugins/PokeAPI/v1/dataStreams/generations.json new file mode 100644 index 00000000..5aed3a72 --- /dev/null +++ b/plugins/PokeAPI/v1/dataStreams/generations.json @@ -0,0 +1,36 @@ +{ + "name": "generations", + "displayName": "Generations", + "description": "Pokémon game generations, from Generation I onwards", + "tags": ["Pokémon", "Generation"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "/generation", + "getArgs": [{ "key": "limit", "value": "100" }], + "pathToData": "results" + }, + "matches": "none", + "timeframes": false, + "visibility": { "type": "hidden" }, + "metadata": [ + { "name": "url", "displayName": "URL", "shape": "url", "visible": false }, + { "name": "name", "displayName": "Slug", "shape": "string", "visible": false }, + { + "name": "id", + "displayName": "ID", + "shape": "string", + "role": "id", + "computed": true, + "valueExpression": "{{ $['url'].split('/').filter(Boolean).pop() }}" + }, + { + "name": "generationName", + "displayName": "Generation", + "shape": "string", + "role": "label", + "computed": true, + "valueExpression": "{{ 'Generation ' + $['name'].split('-').pop().toUpperCase() }}" + } + ] +} diff --git a/plugins/PokeAPI/v1/dataStreams/pokemon.json b/plugins/PokeAPI/v1/dataStreams/pokemon.json new file mode 100644 index 00000000..0a9a90c3 --- /dev/null +++ b/plugins/PokeAPI/v1/dataStreams/pokemon.json @@ -0,0 +1,40 @@ +{ + "name": "pokemon", + "displayName": "Pokémon", + "description": "Every Pokémon species in the National Pokédex", + "tags": ["Pokémon"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "/pokemon-species", + "pathToData": "results", + "paging": { + "mode": "nextUrl", + "pageSize": { "realm": "queryArg", "path": "limit", "value": "200" }, + "in": { "realm": "payload", "path": "next" } + } + }, + "matches": "none", + "timeframes": false, + "visibility": { "type": "hidden" }, + "metadata": [ + { "name": "url", "displayName": "URL", "shape": "url", "visible": false }, + { "name": "name", "displayName": "Slug", "shape": "string", "visible": false }, + { + "name": "id", + "displayName": "Pokédex No.", + "shape": "string", + "role": "id", + "computed": true, + "valueExpression": "{{ $['url'].split('/').filter(Boolean).pop() }}" + }, + { + "name": "pokemonName", + "displayName": "Pokémon", + "shape": "string", + "role": "label", + "computed": true, + "valueExpression": "{{ $['name'].split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ') }}" + } + ] +} diff --git a/plugins/PokeAPI/v1/dataStreams/pokemonByGeneration.json b/plugins/PokeAPI/v1/dataStreams/pokemonByGeneration.json new file mode 100644 index 00000000..bcf87619 --- /dev/null +++ b/plugins/PokeAPI/v1/dataStreams/pokemonByGeneration.json @@ -0,0 +1,23 @@ +{ + "name": "pokemonByGeneration", + "displayName": "Pokémon by Generation", + "description": "Pokémon species introduced in a generation", + "tags": ["Generation", "Pokémon"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/generation/{{object.rawId}}", + "postRequestScript": "pokemonByGeneration.js" + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Pokemon Generation"] } }, + "timeframes": false, + "defaultShaping": { "sort": { "by": [["pokedexNumber", "asc"]] } }, + "metadata": [ + { "name": "generationId", "displayName": "Generation ID", "shape": "string", "visible": false }, + { "name": "generationName", "displayName": "Generation", "shape": "string" }, + { "name": "pokemonId", "displayName": "Pokémon ID", "shape": "string", "visible": false }, + { "name": "pokemonName", "displayName": "Pokémon", "shape": "string", "role": "label" }, + { "name": "pokedexNumber", "displayName": "Pokédex No.", "shape": ["number", { "thousandsSeparator": false }] }, + { "sourceId": "pokemonId", "sourceType": "Pokemon", "name": "pokemonName" } + ] +} diff --git a/plugins/PokeAPI/v1/dataStreams/pokemonByType.json b/plugins/PokeAPI/v1/dataStreams/pokemonByType.json new file mode 100644 index 00000000..94299134 --- /dev/null +++ b/plugins/PokeAPI/v1/dataStreams/pokemonByType.json @@ -0,0 +1,24 @@ +{ + "name": "pokemonByType", + "displayName": "Pokémon by Type", + "description": "Pokémon that have a type, with whether it is their primary or secondary type", + "tags": ["Type", "Pokémon"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/type/{{object.rawId}}", + "postRequestScript": "pokemonByType.js" + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Pokemon Type"] } }, + "timeframes": false, + "defaultShaping": { "sort": { "by": [["pokedexNumber", "asc"]] } }, + "metadata": [ + { "name": "typeId", "displayName": "Type ID", "shape": "string", "visible": false }, + { "name": "typeName", "displayName": "Type", "shape": "string" }, + { "name": "pokemonId", "displayName": "Pokémon ID", "shape": "string", "sourceType": "Pokemon", "visible": false }, + { "name": "pokemonName", "displayName": "Pokémon", "shape": "string", "role": "label", "sourceId": "pokemonId", "sourceType": "Pokemon", "objectPropertyPath": "name" }, + { "name": "pokedexNumber", "displayName": "Pokédex No.", "shape": ["number", { "thousandsSeparator": false }] }, + { "name": "slot", "displayName": "Slot", "shape": "string" }, + { "sourceId": "pokemonId", "sourceType": "Pokemon", "name": "pokemonName" } + ] +} diff --git a/plugins/PokeAPI/v1/dataStreams/pokemonMoves.json b/plugins/PokeAPI/v1/dataStreams/pokemonMoves.json new file mode 100644 index 00000000..fa7a7b03 --- /dev/null +++ b/plugins/PokeAPI/v1/dataStreams/pokemonMoves.json @@ -0,0 +1,24 @@ +{ + "name": "pokemonMoves", + "displayName": "Pokémon Moves", + "description": "Moves a Pokémon can learn in the most recent game it appears in, with learn method and level", + "tags": ["Pokémon", "Moves"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/pokemon/{{object.rawId}}", + "postRequestScript": "pokemonMoves.js" + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Pokemon"] } }, + "timeframes": false, + "defaultShaping": { "sort": { "by": [["learnMethod", "asc"], ["level", "asc"]] } }, + "metadata": [ + { "name": "pokemonId", "displayName": "Pokédex No.", "shape": "string", "visible": false }, + { "sourceId": "pokemonId", "sourceType": "Pokemon", "name": "pokemonName" }, + { "name": "pokemonName", "displayName": "Pokémon", "shape": "string" }, + { "name": "move", "displayName": "Move", "shape": "string", "role": "label" }, + { "name": "learnMethod", "displayName": "Learn Method", "shape": "string" }, + { "name": "level", "displayName": "Level", "shape": ["number", { "decimalPlaces": 0 }] }, + { "name": "game", "displayName": "Game", "shape": "string" } + ] +} diff --git a/plugins/PokeAPI/v1/dataStreams/pokemonProfile.json b/plugins/PokeAPI/v1/dataStreams/pokemonProfile.json new file mode 100644 index 00000000..90588def --- /dev/null +++ b/plugins/PokeAPI/v1/dataStreams/pokemonProfile.json @@ -0,0 +1,32 @@ +{ + "name": "pokemonProfile", + "displayName": "Pokémon Profile", + "description": "Types, size, abilities, base stat total and artwork for a Pokémon", + "tags": ["Pokémon"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/pokemon/{{object.rawId}}", + "postRequestScript": "pokemonProfile.js" + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Pokemon"] } }, + "timeframes": false, + "metadata": [ + { "name": "pokemonId", "displayName": "ID", "shape": "string", "visible": false }, + { "sourceId": "pokemonId", "sourceType": "Pokemon", "name": "pokemonName" }, + { "name": "pokemonName", "displayName": "Pokémon", "shape": "string", "role": "label" }, + { "name": "pokedexNumber", "displayName": "Pokédex No.", "shape": ["number", { "thousandsSeparator": false }] }, + { "name": "primaryType", "displayName": "Primary Type", "shape": "string" }, + { "name": "secondaryType", "displayName": "Secondary Type", "shape": "string" }, + { "name": "types", "displayName": "Types", "shape": "string" }, + { "name": "heightM", "displayName": "Height (m)", "shape": ["number", { "decimalPlaces": 1 }] }, + { "name": "weightKg", "displayName": "Weight (kg)", "shape": ["number", { "decimalPlaces": 1 }] }, + { "name": "baseExperience", "displayName": "Base Experience", "shape": "number" }, + { "name": "abilities", "displayName": "Abilities", "shape": "string" }, + { "name": "hiddenAbility", "displayName": "Hidden Ability", "shape": "string" }, + { "name": "statTotal", "displayName": "Base Stat Total", "shape": "number", "role": "value" }, + { "name": "artworkUrl", "displayName": "Artwork", "shape": ["url", { "label": "Official artwork" }] }, + { "name": "spriteUrl", "displayName": "Sprite", "shape": ["url", { "label": "Sprite" }] }, + { "name": "cryUrl", "displayName": "Cry", "shape": ["url", { "label": "Cry" }] } + ] +} diff --git a/plugins/PokeAPI/v1/dataStreams/pokemonSpecies.json b/plugins/PokeAPI/v1/dataStreams/pokemonSpecies.json new file mode 100644 index 00000000..62df72ec --- /dev/null +++ b/plugins/PokeAPI/v1/dataStreams/pokemonSpecies.json @@ -0,0 +1,169 @@ +{ + "name": "pokemonSpecies", + "displayName": "Pokémon Species", + "description": "Pokédex entry, category, generation, breeding and capture details for a Pokémon species", + "tags": [ + "Pokémon", + "Species" + ], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/pokemon-species/{{object.rawId}}", + "postRequestScript": "pokemonSpecies.js" + }, + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "Pokemon" + ] + } + }, + "timeframes": false, + "metadata": [ + { + "name": "pokemonId", + "displayName": "Pokédex No.", + "shape": "string", + "visible": false, + "role": "id" + }, + { + "sourceId": "pokemonId", + "sourceType": "Pokemon", + "name": "pokemonName" + }, + { + "name": "pokemonName", + "displayName": "Pokémon", + "shape": "string", + "role": "label" + }, + { + "name": "genus", + "displayName": "Category", + "shape": "string" + }, + { + "name": "generation", + "displayName": "Generation", + "shape": "string" + }, + { + "name": "rarity", + "displayName": "Rarity", + "shape": "string" + }, + { + "name": "isLegendary", + "displayName": "Legendary", + "shape": "boolean" + }, + { + "name": "isMythical", + "displayName": "Mythical", + "shape": "boolean" + }, + { + "name": "isBaby", + "displayName": "Baby", + "shape": "boolean" + }, + { + "name": "captureRate", + "displayName": "Capture Rate", + "shape": [ + "number", + { + "decimalPlaces": 0 + } + ] + }, + { + "name": "baseHappiness", + "displayName": "Base Happiness", + "shape": [ + "number", + { + "decimalPlaces": 0 + } + ] + }, + { + "name": "femalePercent", + "displayName": "Female", + "shape": [ + "percent", + { + "decimalPlaces": 1 + } + ] + }, + { + "name": "genderless", + "displayName": "Genderless", + "shape": "boolean" + }, + { + "name": "eggCycles", + "displayName": "Egg Cycles", + "shape": [ + "number", + { + "decimalPlaces": 0 + } + ] + }, + { + "name": "growthRate", + "displayName": "Growth Rate", + "shape": "string" + }, + { + "name": "habitat", + "displayName": "Habitat", + "shape": "string" + }, + { + "name": "colour", + "displayName": "Colour", + "shape": "string" + }, + { + "name": "bodyShape", + "displayName": "Body Shape", + "shape": "string" + }, + { + "name": "eggGroups", + "displayName": "Egg Groups", + "shape": "string" + }, + { + "name": "evolvesFrom", + "displayName": "Evolves From", + "shape": "string" + }, + { + "name": "pokedexEntry", + "displayName": "Pokédex Entry", + "shape": "string" + }, + { + "name": "pokedexEntryGame", + "displayName": "Entry Game", + "shape": "string" + }, + { + "name": "formCount", + "displayName": "Forms", + "shape": [ + "number", + { + "decimalPlaces": 0 + } + ] + } + ] +} diff --git a/plugins/PokeAPI/v1/dataStreams/pokemonStats.json b/plugins/PokeAPI/v1/dataStreams/pokemonStats.json new file mode 100644 index 00000000..07578022 --- /dev/null +++ b/plugins/PokeAPI/v1/dataStreams/pokemonStats.json @@ -0,0 +1,24 @@ +{ + "name": "pokemonStats", + "displayName": "Pokémon Stats", + "description": "Base stats and effort value yield for a Pokémon, one row per stat", + "tags": ["Pokémon", "Stats"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/pokemon/{{object.rawId}}", + "postRequestScript": "pokemonStats.js" + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Pokemon"] } }, + "timeframes": false, + "defaultShaping": { "sort": { "by": [["statOrder", "asc"]] } }, + "metadata": [ + { "name": "pokemonId", "displayName": "Pokédex No.", "shape": "string", "visible": false }, + { "name": "pokemonName", "displayName": "Pokémon", "shape": "string" }, + { "name": "stat", "displayName": "Stat", "shape": "string", "role": "label" }, + { "name": "statOrder", "displayName": "Stat Order", "shape": "number", "visible": false }, + { "name": "baseStat", "displayName": "Base Stat", "shape": ["number", { "decimalPlaces": 0 }], "role": "value" }, + { "name": "effortYield", "displayName": "EV Yield", "shape": ["number", { "decimalPlaces": 0 }] }, + { "sourceId": "pokemonId", "sourceType": "Pokemon", "name": "pokemonName" } + ] +} diff --git a/plugins/PokeAPI/v1/dataStreams/scripts/generationSummary.js b/plugins/PokeAPI/v1/dataStreams/scripts/generationSummary.js new file mode 100644 index 00000000..dc84b5ee --- /dev/null +++ b/plugins/PokeAPI/v1/dataStreams/scripts/generationSummary.js @@ -0,0 +1,26 @@ +const titleCase = (slug) => + String(slug || '') + .split('-') + .filter(Boolean) + .map((w) => w.charAt(0).toUpperCase() + w.slice(1)) + .join(' '); + +const english = (data.names || []).find((n) => n.language && n.language.name === 'en'); + +// Match the indexed types: Stellar, Unknown and Shadow have no Pokémon or matchups +const excludedTypes = ['stellar', 'unknown', 'shadow']; +const battleTypes = (data.types || []).filter((t) => !excludedTypes.includes(t.name)); + +result = [ + { + generationId: String(data.id), + generationName: english ? english.name : titleCase(data.name), + region: titleCase(data.main_region && data.main_region.name), + games: (data.version_groups || []).map((g) => titleCase(g.name)).join(', '), + newSpecies: (data.pokemon_species || []).length, + newMoves: (data.moves || []).length, + newAbilities: (data.abilities || []).length, + newTypes: battleTypes.length, + typesIntroduced: battleTypes.map((t) => titleCase(t.name)).join(', ') + } +]; diff --git a/plugins/PokeAPI/v1/dataStreams/scripts/pokemonByGeneration.js b/plugins/PokeAPI/v1/dataStreams/scripts/pokemonByGeneration.js new file mode 100644 index 00000000..89e10d98 --- /dev/null +++ b/plugins/PokeAPI/v1/dataStreams/scripts/pokemonByGeneration.js @@ -0,0 +1,15 @@ +// pathToData cannot reach the parent generation fields, so carry them onto each species row +const english = (data.names || []).find((n) => n.language && n.language.name === "en"); +const generationName = english ? english.name : data.name; +const titleCase = (slug) => slug.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" "); + +result = (data.pokemon_species || []).map((s) => { + const pokemonId = s.url.split("/").filter(Boolean).pop(); + return { + generationId: String(data.id), + generationName, + pokemonId, + pokemonName: titleCase(s.name), + pokedexNumber: Number(pokemonId) + }; +}); diff --git a/plugins/PokeAPI/v1/dataStreams/scripts/pokemonByType.js b/plugins/PokeAPI/v1/dataStreams/scripts/pokemonByType.js new file mode 100644 index 00000000..bb53a2ef --- /dev/null +++ b/plugins/PokeAPI/v1/dataStreams/scripts/pokemonByType.js @@ -0,0 +1,15 @@ +// The type endpoint returns one body per type; rows are derived from the body, not context.objects +const english = (data.names || []).find((n) => n.language && n.language.name === "en"); +const typeName = english ? english.name : data.name; + +// Alternate forms (Mega, Gigantamax, regional) have ids >= 10000 and are not indexed as Pokemon, +// so keeping them would inflate per-type counts. Ids below 10000 equal the Pokemon object's rawId. +result = (data.pokemon || []) + .map((entry) => ({ + typeId: String(data.id), + typeName, + pokemonId: entry.pokemon.url.split("/").filter(Boolean).pop(), + slot: entry.slot === 1 ? "Primary" : "Secondary" + })) + .filter((row) => Number(row.pokemonId) < 10000) + .map((row) => ({ ...row, pokedexNumber: Number(row.pokemonId) })); diff --git a/plugins/PokeAPI/v1/dataStreams/scripts/pokemonMoves.js b/plugins/PokeAPI/v1/dataStreams/scripts/pokemonMoves.js new file mode 100644 index 00000000..5fe8a5d4 --- /dev/null +++ b/plugins/PokeAPI/v1/dataStreams/scripts/pokemonMoves.js @@ -0,0 +1,47 @@ +// Version-group ids are chronological except these two retro Japanese releases, +// which were added to the API out of sequence and would otherwise look "newest" +const excluded = ["red-green-japan", "blue-japan"]; + +const titleCase = (slug) => + String(slug || "") + .split("-") + .map((w) => w.charAt(0).toUpperCase() + w.slice(1)) + .join(" "); +const groupId = (vg) => Number(String(vg.url).split("/").filter(Boolean).pop()); + +// Flatten to one entry per (move, version group, method) +const entries = (data.moves || []).flatMap((m) => + (m.version_group_details || []) + .filter((d) => !excluded.includes(d.version_group.name)) + .map((d) => ({ move: m.move.name, detail: d, id: groupId(d.version_group) })), +); + +// Prefer the newest game with a real learnset: some recent groups (e.g. champions) +// only carry the "train" method, so require at least one level-up entry. Fall back +// to the newest group of any kind if none has level-up data. +const pickNewest = (list) => + list.reduce((best, e) => (best === null || e.id > best ? e.id : best), null); +const levelUpGroup = pickNewest(entries.filter((e) => e.detail.move_learn_method.name === "level-up")); +const chosen = levelUpGroup !== null ? levelUpGroup : pickNewest(entries); + +if (chosen === null) { + result = []; +} else { + const pokemonName = titleCase(data.species && data.species.name); + result = entries + .filter((e) => e.id === chosen) + .map((e) => { + const isLevelUp = e.detail.move_learn_method.name === "level-up"; + // Since Generation VII a level-up move at level 0 is learned on evolving + const onEvolution = isLevelUp && e.detail.level_learned_at === 0; + return { + pokemonId: String(data.id), + pokemonName, + move: titleCase(e.move), + learnMethod: onEvolution ? "Evolution" : titleCase(e.detail.move_learn_method.name), + // The API reports 0 for non-level-up methods, which is not a real level + level: isLevelUp && !onEvolution ? e.detail.level_learned_at : null, + game: titleCase(e.detail.version_group.name), + }; + }); +} diff --git a/plugins/PokeAPI/v1/dataStreams/scripts/pokemonProfile.js b/plugins/PokeAPI/v1/dataStreams/scripts/pokemonProfile.js new file mode 100644 index 00000000..ffd2a7df --- /dev/null +++ b/plugins/PokeAPI/v1/dataStreams/scripts/pokemonProfile.js @@ -0,0 +1,33 @@ +const title = (slug) => + (slug || "") + .split("-") + .map((w) => w.charAt(0).toUpperCase() + w.slice(1)) + .join(" "); + +const typeNames = (data.types || []) + .slice() + .sort((a, b) => a.slot - b.slot) + .map((t) => title(t.type.name)); +const abilities = data.abilities || []; +const visible = abilities.filter((a) => !a.is_hidden).sort((a, b) => a.slot - b.slot); +const hidden = abilities.find((a) => a.is_hidden); + +result = [ + { + pokemonId: String(data.id), + pokemonName: title(data.species && data.species.name), + pokedexNumber: data.id, + primaryType: typeNames[0] || null, + secondaryType: typeNames[1] || null, + types: typeNames.join(" / "), + heightM: data.height / 10, + weightKg: data.weight / 10, + baseExperience: data.base_experience, + abilities: visible.map((a) => title(a.ability.name)).join(", "), + hiddenAbility: hidden ? title(hidden.ability.name) : null, + statTotal: (data.stats || []).reduce((s, x) => s + x.base_stat, 0), + artworkUrl: _.get(data, ["sprites", "other", "official-artwork", "front_default"], null), + spriteUrl: _.get(data, ["sprites", "front_default"], null), + cryUrl: _.get(data, ["cries", "latest"], null), + }, +]; diff --git a/plugins/PokeAPI/v1/dataStreams/scripts/pokemonSpecies.js b/plugins/PokeAPI/v1/dataStreams/scripts/pokemonSpecies.js new file mode 100644 index 00000000..00e58c54 --- /dev/null +++ b/plugins/PokeAPI/v1/dataStreams/scripts/pokemonSpecies.js @@ -0,0 +1,48 @@ +// One row per species. A script is needed because names, genera and flavour text are +// arrays of per-language entries that must be filtered to English and reduced to one value. +const title = (slug) => + slug + ? slug + .split("-") + .map((w) => w.charAt(0).toUpperCase() + w.slice(1)) + .join(" ") + : null; + +const english = (entries) => (entries || []).filter((e) => e.language && e.language.name === "en"); + +const englishName = english(data.names)[0]; +const englishGenus = english(data.genera)[0]; + +// Flavour text is ordered oldest to newest, so the last English entry is the most recent game +const entries = english(data.flavor_text_entries); +const latest = entries[entries.length - 1]; + +// gender_rate is the chance of being female in eighths; -1 means genderless +const genderless = data.gender_rate === -1; + +result = [ + { + pokemonId: String(data.id), + pokemonName: englishName ? englishName.name : title(data.name), + genus: englishGenus ? englishGenus.genus : null, + generation: data.generation ? "Generation " + data.generation.name.split("-").pop().toUpperCase() : null, + isLegendary: !!data.is_legendary, + isMythical: !!data.is_mythical, + isBaby: !!data.is_baby, + rarity: data.is_legendary ? "Legendary" : data.is_mythical ? "Mythical" : data.is_baby ? "Baby" : "Standard", + captureRate: data.capture_rate, + baseHappiness: data.base_happiness, + femalePercent: genderless ? null : (data.gender_rate / 8) * 100, + genderless, + eggCycles: data.hatch_counter, + growthRate: title(data.growth_rate && data.growth_rate.name), + habitat: title(data.habitat && data.habitat.name), + colour: title(data.color && data.color.name), + bodyShape: title(data.shape && data.shape.name), + eggGroups: (data.egg_groups || []).map((g) => title(g.name)).join(", "), + evolvesFrom: title(data.evolves_from_species && data.evolves_from_species.name), + pokedexEntry: latest ? latest.flavor_text.replace(/\s+/g, " ").trim() : null, + pokedexEntryGame: latest ? title(latest.version.name) : null, + formCount: (data.varieties || []).length, + }, +]; diff --git a/plugins/PokeAPI/v1/dataStreams/scripts/pokemonStats.js b/plugins/PokeAPI/v1/dataStreams/scripts/pokemonStats.js new file mode 100644 index 00000000..848a1f7c --- /dev/null +++ b/plugins/PokeAPI/v1/dataStreams/scripts/pokemonStats.js @@ -0,0 +1,20 @@ +const titleCase = (slug) => + slug.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" "); + +const labels = { + hp: "HP", + attack: "Attack", + defense: "Defense", + "special-attack": "Sp. Atk", + "special-defense": "Sp. Def", + speed: "Speed", +}; + +result = (data.stats || []).map((s, i) => ({ + pokemonId: String(data.id), + pokemonName: titleCase(data.species.name), + stat: labels[s.stat.name] || titleCase(s.stat.name), + statOrder: i + 1, + baseStat: s.base_stat, + effortYield: s.effort, +})); diff --git a/plugins/PokeAPI/v1/dataStreams/scripts/typeMatchups.js b/plugins/PokeAPI/v1/dataStreams/scripts/typeMatchups.js new file mode 100644 index 00000000..b36f2451 --- /dev/null +++ b/plugins/PokeAPI/v1/dataStreams/scripts/typeMatchups.js @@ -0,0 +1,32 @@ +// damage_relations is an object of six arrays, not a list of rows, so each (relation, other type) +// pair has to be unrolled into its own row. Multiplier and wording are fixed per relation key. +const relations = { + double_damage_to: { direction: "Attacking", multiplier: 2, effectiveness: "Super effective", relation: "Double damage to" }, + half_damage_to: { direction: "Attacking", multiplier: 0.5, effectiveness: "Not very effective", relation: "Half damage to" }, + no_damage_to: { direction: "Attacking", multiplier: 0, effectiveness: "No effect", relation: "No damage to" }, + double_damage_from: { direction: "Defending", multiplier: 2, effectiveness: "Super effective", relation: "Double damage from" }, + half_damage_from: { direction: "Defending", multiplier: 0.5, effectiveness: "Not very effective", relation: "Half damage from" }, + no_damage_from: { direction: "Defending", multiplier: 0, effectiveness: "No effect", relation: "No damage from" }, +}; + +// The slug ("fire") is title-cased; the localised name is only used for the scoped type itself +const titleCase = (slug) => slug.charAt(0).toUpperCase() + slug.slice(1); +const idFromUrl = (url) => url.split("/").filter(Boolean).pop(); + +// Identity comes from the response body, so it stays correct when a tile is scoped to several types +const english = (data.names || []).find((n) => n.language && n.language.name === "en"); +const typeName = english ? english.name : titleCase(data.name); +const typeId = String(data.id); + +result = Object.entries(relations).flatMap(([key, rel]) => + ((data.damage_relations || {})[key] || []).map((other) => ({ + typeId, + typeName, + direction: rel.direction, + multiplier: rel.multiplier, + effectiveness: rel.effectiveness, + relation: rel.relation, + otherTypeId: idFromUrl(other.url), + otherTypeName: titleCase(other.name), + })), +); diff --git a/plugins/PokeAPI/v1/dataStreams/scripts/types.js b/plugins/PokeAPI/v1/dataStreams/scripts/types.js new file mode 100644 index 00000000..cf76df0f --- /dev/null +++ b/plugins/PokeAPI/v1/dataStreams/scripts/types.js @@ -0,0 +1,4 @@ +// Stellar (Tera-only), Unknown and Shadow are listed by the API but have no Pokémon or matchups +const excluded = ["stellar", "unknown", "shadow"]; + +result = (data.results || []).filter((t) => !excluded.includes(t.name)); diff --git a/plugins/PokeAPI/v1/dataStreams/typeMatchups.json b/plugins/PokeAPI/v1/dataStreams/typeMatchups.json new file mode 100644 index 00000000..8919dc19 --- /dev/null +++ b/plugins/PokeAPI/v1/dataStreams/typeMatchups.json @@ -0,0 +1,26 @@ +{ + "name": "typeMatchups", + "displayName": "Type Matchups", + "description": "Damage multipliers a type deals to and takes from every other type", + "tags": ["Type"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/type/{{object.rawId}}", + "postRequestScript": "typeMatchups.js" + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Pokemon Type"] } }, + "timeframes": false, + "defaultShaping": { "sort": { "by": [["direction", "asc"], ["multiplier", "desc"]] } }, + "metadata": [ + { "name": "typeId", "displayName": "Type ID", "shape": "string", "visible": false }, + { "name": "typeName", "displayName": "Type", "shape": "string", "role": "label" }, + { "name": "direction", "displayName": "Direction", "shape": "string" }, + { "name": "multiplier", "displayName": "Multiplier", "shape": ["number", { "decimalPlaces": 1 }], "role": "value" }, + { "name": "effectiveness", "displayName": "Effectiveness", "shape": "string" }, + { "name": "relation", "displayName": "Relation", "shape": "string" }, + { "name": "otherTypeId", "displayName": "Other Type ID", "shape": "string", "visible": false }, + { "name": "otherTypeName", "displayName": "Other Type", "shape": "string" }, + { "sourceId": "otherTypeId", "sourceType": "Pokemon Type", "name": "otherTypeName" } + ] +} diff --git a/plugins/PokeAPI/v1/dataStreams/types.json b/plugins/PokeAPI/v1/dataStreams/types.json new file mode 100644 index 00000000..23d07c79 --- /dev/null +++ b/plugins/PokeAPI/v1/dataStreams/types.json @@ -0,0 +1,36 @@ +{ + "name": "types", + "displayName": "Types", + "description": "Pokémon battle types (excludes Stellar, Unknown and Shadow, which have no Pokémon)", + "tags": ["Pokémon", "Type"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "/type", + "getArgs": [{ "key": "limit", "value": "100" }], + "postRequestScript": "types.js" + }, + "matches": "none", + "timeframes": false, + "visibility": { "type": "hidden" }, + "metadata": [ + { "name": "url", "displayName": "URL", "shape": "url", "visible": false }, + { "name": "name", "displayName": "Slug", "shape": "string", "visible": false }, + { + "name": "id", + "displayName": "ID", + "shape": "string", + "role": "id", + "computed": true, + "valueExpression": "{{ $['url'].split('/').filter(Boolean).pop() }}" + }, + { + "name": "typeName", + "displayName": "Type", + "shape": "string", + "role": "label", + "computed": true, + "valueExpression": "{{ $['name'].charAt(0).toUpperCase() + $['name'].slice(1) }}" + } + ] +} diff --git a/plugins/PokeAPI/v1/defaultContent/generation.dash.json b/plugins/PokeAPI/v1/defaultContent/generation.dash.json new file mode 100644 index 00000000..935f6127 --- /dev/null +++ b/plugins/PokeAPI/v1/defaultContent/generation.dash.json @@ -0,0 +1,293 @@ +{ + "name": "Generation", + "schemaVersion": "1.5", + "timeframe": "none", + "variables": [ + "{{variables.[Generation]}}" + ], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "d4e5226c-939f-4367-9012-2004b5e44308", + "x": 0, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Region", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[generationSummary]}}", + "name": "generationSummary", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[Generations]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Generation]}}" + }, + "variables": [ + "{{variables.[Generation]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "region", + "comparisonColumn": "none" + } + } + } + } + }, + { + "i": "51be13e5-693c-489b-9703-a21f249dcd0c", + "x": 1, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "New Species", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[generationSummary]}}", + "name": "generationSummary", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[Generations]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Generation]}}" + }, + "variables": [ + "{{variables.[Generation]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "newSpecies", + "comparisonColumn": "none" + } + } + } + } + }, + { + "i": "e25cf818-b860-44d2-a1d4-796731977fca", + "x": 2, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "New Moves", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[generationSummary]}}", + "name": "generationSummary", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[Generations]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Generation]}}" + }, + "variables": [ + "{{variables.[Generation]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "newMoves", + "comparisonColumn": "none" + } + } + } + } + }, + { + "i": "529edfd7-d18f-43ee-9597-4141f32a61c8", + "x": 3, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "New Abilities", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[generationSummary]}}", + "name": "generationSummary", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[Generations]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Generation]}}" + }, + "variables": [ + "{{variables.[Generation]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "newAbilities", + "comparisonColumn": "none" + } + } + } + } + }, + { + "i": "61ea6346-6eea-460b-b12d-a211371b8caa", + "x": 0, + "y": 2, + "w": 4, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Generation Details", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[generationSummary]}}", + "name": "generationSummary", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[Generations]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Generation]}}" + }, + "variables": [ + "{{variables.[Generation]}}" + ], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true, + "columnOrder": [ + "games", + "newTypes", + "typesIntroduced" + ], + "hiddenColumns": [ + "generationId", + "generationName", + "region", + "newSpecies", + "newMoves", + "newAbilities" + ] + } + } + } + } + }, + { + "i": "f8fe6713-811b-403c-9f61-eafe24064669", + "x": 0, + "y": 5, + "w": 4, + "h": 6, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Species Introduced", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[pokemonByGeneration]}}", + "name": "pokemonByGeneration", + "pluginConfigId": "{{configId}}", + "sort": { + "by": [ + [ + "pokedexNumber", + "asc" + ] + ] + } + }, + "scope": { + "scope": "{{scopes.[Generations]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Generation]}}" + }, + "variables": [ + "{{variables.[Generation]}}" + ], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "pokedexNumber", + "pokemonName" + ], + "hiddenColumns": [ + "generationId", + "generationName", + "pokemonId" + ], + "rowLinkColumnName": { + "value": "pokemonName", + "label": "Pokémon" + } + } + } + } + } + } + ] + } +} diff --git a/plugins/PokeAPI/v1/defaultContent/manifest.json b/plugins/PokeAPI/v1/defaultContent/manifest.json new file mode 100644 index 00000000..30a2086f --- /dev/null +++ b/plugins/PokeAPI/v1/defaultContent/manifest.json @@ -0,0 +1,8 @@ +{ + "items": [ + { "name": "overview", "type": "dashboard" }, + { "name": "pokemon", "type": "dashboard" }, + { "name": "pokemonType", "type": "dashboard" }, + { "name": "generation", "type": "dashboard" } + ] +} diff --git a/plugins/PokeAPI/v1/defaultContent/overview.dash.json b/plugins/PokeAPI/v1/defaultContent/overview.dash.json new file mode 100644 index 00000000..a5b54085 --- /dev/null +++ b/plugins/PokeAPI/v1/defaultContent/overview.dash.json @@ -0,0 +1,430 @@ +{ + "name": "Overview", + "schemaVersion": "1.5", + "timeframe": "none", + "variables": [], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "e5d6e761-2c9e-4b2c-b5f1-e08f1cb9ec05", + "x": 0, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Pokémon", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[pokemonByGeneration]}}", + "name": "pokemonByGeneration", + "pluginConfigId": "{{configId}}", + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "scope": { + "scope": "{{scopes.[Generations]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none", + "label": "species" + } + } + } + } + }, + { + "i": "c94e4559-ef32-4018-8077-44b89f5c70ea", + "x": 1, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Types", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[pokemonByType]}}", + "name": "pokemonByType", + "pluginConfigId": "{{configId}}", + "group": { + "by": [], + "aggregate": [ + { + "type": "distinctCount", + "names": [ + "typeName" + ] + } + ] + } + }, + "scope": { + "scope": "{{scopes.[Pokémon Types]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "typeName_distinctCount", + "comparisonColumn": "none", + "label": "battle types" + } + } + } + } + }, + { + "i": "ce2b0c47-0e63-467a-888a-1370eebf1deb", + "x": 2, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Generations", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[generationSummary]}}", + "name": "generationSummary", + "pluginConfigId": "{{configId}}", + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "scope": { + "scope": "{{scopes.[Generations]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none", + "label": "generations" + } + } + } + } + }, + { + "i": "24a53298-a086-4eb6-a812-cc06e4d6494d", + "x": 3, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Moves", + "description": "Moves introduced across every generation", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[generationSummary]}}", + "name": "generationSummary", + "pluginConfigId": "{{configId}}", + "group": { + "by": [], + "aggregate": [ + { + "type": "sum", + "names": [ + "newMoves" + ] + } + ] + } + }, + "scope": { + "scope": "{{scopes.[Generations]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "newMoves_sum", + "comparisonColumn": "none", + "label": "moves" + } + } + } + } + }, + { + "i": "c81cb9df-0e10-41be-b789-06b5db700415", + "x": 0, + "y": 2, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Species per Generation", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[pokemonByGeneration]}}", + "name": "pokemonByGeneration", + "pluginConfigId": "{{configId}}", + "group": { + "by": [ + [ + "generationName", + "uniqueValues" + ], + [ + "generationId", + "uniqueValues" + ] + ], + "aggregate": [ + { + "type": "count" + } + ] + }, + "sort": { + "by": [ + [ + "generationId_uniqueValues", + "asc" + ] + ] + } + }, + "scope": { + "scope": "{{scopes.[Generations]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-bar-chart", + "config": { + "data-stream-bar-chart": { + "xAxisData": "generationName_uniqueValues", + "yAxisData": [ + "count" + ], + "xAxisGroup": "none", + "xAxisLabel": "", + "yAxisLabel": "Species", + "showXAxisLabel": false, + "showYAxisLabel": true, + "showLegend": false, + "legendPosition": "bottom", + "showGrid": true, + "horizontalLayout": "vertical", + "displayMode": "actual", + "showTotals": false, + "showValue": true, + "grouping": false, + "range": { + "type": "auto" + } + } + } + } + } + }, + { + "i": "fe6a113f-a472-4e93-813e-b417d9c024a1", + "x": 2, + "y": 2, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Pokémon per Primary Type", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[pokemonByType]}}", + "name": "pokemonByType", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "slot", + "operation": "equals", + "value": "Primary" + } + ] + }, + "group": { + "by": [ + [ + "typeName", + "uniqueValues" + ] + ], + "aggregate": [ + { + "type": "count" + } + ] + }, + "sort": { + "by": [ + [ + "count", + "desc" + ] + ] + } + }, + "scope": { + "scope": "{{scopes.[Pokémon Types]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-bar-chart", + "config": { + "data-stream-bar-chart": { + "xAxisData": "typeName_uniqueValues", + "yAxisData": [ + "count" + ], + "xAxisGroup": "none", + "xAxisLabel": "", + "yAxisLabel": "Pokémon", + "showXAxisLabel": false, + "showYAxisLabel": true, + "showLegend": false, + "legendPosition": "bottom", + "showGrid": true, + "horizontalLayout": "horizontal", + "displayMode": "actual", + "showTotals": false, + "showValue": true, + "grouping": false, + "range": { + "type": "auto" + } + } + } + } + } + }, + { + "i": "b22f0684-249d-4e32-81f5-a08baa640e2a", + "x": 0, + "y": 6, + "w": 4, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Generation Summary", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[generationSummary]}}", + "name": "generationSummary", + "pluginConfigId": "{{configId}}", + "sort": { + "by": [ + [ + "generationId", + "asc" + ] + ] + } + }, + "scope": { + "scope": "{{scopes.[Generations]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "generationName", + "region", + "newSpecies", + "newMoves", + "newAbilities", + "games" + ], + "hiddenColumns": [ + "generationId", + "newTypes", + "typesIntroduced" + ], + "rowLinkColumnName": { + "value": "generationName", + "label": "Generation" + } + } + } + } + } + } + ] + } +} diff --git a/plugins/PokeAPI/v1/defaultContent/pokemon.dash.json b/plugins/PokeAPI/v1/defaultContent/pokemon.dash.json new file mode 100644 index 00000000..a2c96cad --- /dev/null +++ b/plugins/PokeAPI/v1/defaultContent/pokemon.dash.json @@ -0,0 +1,561 @@ +{ + "name": "Pokémon", + "schemaVersion": "1.5", + "timeframe": "none", + "variables": [ + "{{variables.[Pokémon]}}" + ], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "46f2a2a4-dd00-4edb-8427-67e77fef0ff5", + "x": 0, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Types", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[pokemonProfile]}}", + "name": "pokemonProfile", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[All Pokémon]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Pokémon]}}" + }, + "variables": [ + "{{variables.[Pokémon]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "types", + "comparisonColumn": "none" + } + } + } + } + }, + { + "i": "52457286-4349-4beb-9f5f-c36475081063", + "x": 1, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Category", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[pokemonSpecies]}}", + "name": "pokemonSpecies", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[All Pokémon]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Pokémon]}}" + }, + "variables": [ + "{{variables.[Pokémon]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "genus", + "comparisonColumn": "none" + } + } + } + } + }, + { + "i": "c9a2d2ea-6373-4560-8b85-9ee121c255e9", + "x": 2, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Generation", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[pokemonSpecies]}}", + "name": "pokemonSpecies", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[All Pokémon]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Pokémon]}}" + }, + "variables": [ + "{{variables.[Pokémon]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "generation", + "comparisonColumn": "none" + } + } + } + } + }, + { + "i": "d7bfa116-47a6-450e-b71d-703385e4258e", + "x": 3, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Base Stat Total", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[pokemonProfile]}}", + "name": "pokemonProfile", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[All Pokémon]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Pokémon]}}" + }, + "variables": [ + "{{variables.[Pokémon]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "statTotal", + "comparisonColumn": "none" + } + } + } + } + }, + { + "i": "ef049825-6169-40ec-b129-3b852b357a1f", + "x": 0, + "y": 2, + "w": 4, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Pokédex Entry", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[pokemonSpecies]}}", + "name": "pokemonSpecies", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[All Pokémon]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Pokémon]}}" + }, + "variables": [ + "{{variables.[Pokémon]}}" + ], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true, + "columnOrder": [ + "pokedexEntry", + "pokedexEntryGame" + ], + "hiddenColumns": [ + "pokemonId", + "pokemonName", + "genus", + "generation", + "rarity", + "isLegendary", + "isMythical", + "isBaby", + "captureRate", + "baseHappiness", + "femalePercent", + "genderless", + "eggCycles", + "growthRate", + "habitat", + "colour", + "bodyShape", + "eggGroups", + "evolvesFrom", + "formCount" + ] + } + } + } + } + }, + { + "i": "61e211f8-88c3-409d-9ccc-f79a9ec970ba", + "x": 0, + "y": 4, + "w": 2, + "h": 5, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Base Stats", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[pokemonStats]}}", + "name": "pokemonStats", + "pluginConfigId": "{{configId}}", + "sort": { + "by": [ + [ + "statOrder", + "asc" + ] + ] + } + }, + "scope": { + "scope": "{{scopes.[All Pokémon]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Pokémon]}}" + }, + "variables": [ + "{{variables.[Pokémon]}}" + ], + "visualisation": { + "type": "data-stream-bar-chart", + "config": { + "data-stream-bar-chart": { + "xAxisData": "stat", + "yAxisData": [ + "baseStat" + ], + "xAxisGroup": "none", + "xAxisLabel": "", + "yAxisLabel": "Base stat", + "showXAxisLabel": false, + "showYAxisLabel": true, + "showLegend": false, + "legendPosition": "bottom", + "showGrid": true, + "horizontalLayout": "vertical", + "displayMode": "actual", + "showTotals": false, + "showValue": true, + "grouping": false, + "range": { + "type": "auto" + } + } + } + } + } + }, + { + "i": "6ef68602-db72-40f9-ba0f-e6f24fa58c0d", + "x": 2, + "y": 4, + "w": 1, + "h": 5, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Profile", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[pokemonProfile]}}", + "name": "pokemonProfile", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[All Pokémon]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Pokémon]}}" + }, + "variables": [ + "{{variables.[Pokémon]}}" + ], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true, + "columnOrder": [ + "pokedexNumber", + "heightM", + "weightKg", + "abilities", + "hiddenAbility", + "baseExperience", + "artworkUrl", + "spriteUrl", + "cryUrl" + ], + "hiddenColumns": [ + "pokemonId", + "pokemonName", + "primaryType", + "secondaryType", + "types", + "statTotal" + ] + } + } + } + } + }, + { + "i": "c219a921-9f1d-4e09-99d6-9e93bdd2c4b8", + "x": 3, + "y": 4, + "w": 1, + "h": 5, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Species Details", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[pokemonSpecies]}}", + "name": "pokemonSpecies", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[All Pokémon]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Pokémon]}}" + }, + "variables": [ + "{{variables.[Pokémon]}}" + ], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true, + "columnOrder": [ + "rarity", + "evolvesFrom", + "habitat", + "colour", + "bodyShape", + "captureRate", + "baseHappiness", + "femalePercent", + "genderless", + "eggGroups", + "eggCycles", + "growthRate", + "formCount" + ], + "hiddenColumns": [ + "pokemonId", + "pokemonName", + "genus", + "generation", + "isLegendary", + "isMythical", + "isBaby", + "pokedexEntry", + "pokedexEntryGame" + ] + } + } + } + } + }, + { + "i": "4754bedc-3227-4846-8517-ea6887794c54", + "x": 0, + "y": 9, + "w": 3, + "h": 6, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Moves", + "description": "Moves learnable in the most recent game the Pokémon appears in", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[pokemonMoves]}}", + "name": "pokemonMoves", + "pluginConfigId": "{{configId}}", + "sort": { + "by": [ + [ + "learnMethod", + "asc" + ], + [ + "level", + "asc" + ], + [ + "move", + "asc" + ] + ] + } + }, + "scope": { + "scope": "{{scopes.[All Pokémon]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Pokémon]}}" + }, + "variables": [ + "{{variables.[Pokémon]}}" + ], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "move", + "learnMethod", + "level", + "game" + ], + "hiddenColumns": [ + "pokemonId", + "pokemonName" + ] + } + } + } + } + }, + { + "i": "d12b4fc5-14a8-4317-8b07-531d6448211e", + "x": 3, + "y": 9, + "w": 1, + "h": 6, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Moves by Learn Method", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[pokemonMoves]}}", + "name": "pokemonMoves", + "pluginConfigId": "{{configId}}", + "group": { + "by": [ + [ + "learnMethod", + "uniqueValues" + ] + ], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "scope": { + "scope": "{{scopes.[All Pokémon]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Pokémon]}}" + }, + "variables": [ + "{{variables.[Pokémon]}}" + ], + "visualisation": { + "type": "data-stream-donut-chart", + "config": { + "data-stream-donut-chart": { + "valueColumn": "count", + "labelColumn": "learnMethod_uniqueValues", + "hideCenterValue": false, + "showValuesAsPercentage": false, + "legendPosition": "auto", + "legendMode": "table" + } + } + } + } + } + ] + } +} diff --git a/plugins/PokeAPI/v1/defaultContent/pokemonType.dash.json b/plugins/PokeAPI/v1/defaultContent/pokemonType.dash.json new file mode 100644 index 00000000..999a32a0 --- /dev/null +++ b/plugins/PokeAPI/v1/defaultContent/pokemonType.dash.json @@ -0,0 +1,407 @@ +{ + "name": "Pokémon Type", + "schemaVersion": "1.5", + "timeframe": "none", + "variables": [ + "{{variables.[Pokémon Type]}}" + ], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "c3268220-7d94-454a-bfee-f191783a0732", + "x": 0, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Total Pokémon", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[pokemonByType]}}", + "name": "pokemonByType", + "pluginConfigId": "{{configId}}", + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "scope": { + "scope": "{{scopes.[Pokémon Types]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Pokémon Type]}}" + }, + "variables": [ + "{{variables.[Pokémon Type]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none", + "label": "with this type" + } + } + } + } + }, + { + "i": "e865c706-7685-477a-88d7-058ba1769cdc", + "x": 0, + "y": 2, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Weak to", + "description": "Attacking types that deal double damage to this type", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[typeMatchups]}}", + "name": "typeMatchups", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "relation", + "operation": "equals", + "value": "Double damage from" + } + ] + }, + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "scope": { + "scope": "{{scopes.[Pokémon Types]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Pokémon Type]}}" + }, + "variables": [ + "{{variables.[Pokémon Type]}}" + ], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none", + "label": "types" + } + } + } + } + }, + { + "i": "358110a7-5502-40a5-9da2-60de10164959", + "x": 1, + "y": 0, + "w": 1, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Primary vs Secondary", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[pokemonByType]}}", + "name": "pokemonByType", + "pluginConfigId": "{{configId}}", + "group": { + "by": [ + [ + "slot", + "uniqueValues" + ] + ], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "scope": { + "scope": "{{scopes.[Pokémon Types]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Pokémon Type]}}" + }, + "variables": [ + "{{variables.[Pokémon Type]}}" + ], + "visualisation": { + "type": "data-stream-donut-chart", + "config": { + "data-stream-donut-chart": { + "valueColumn": "count", + "labelColumn": "slot_uniqueValues", + "hideCenterValue": false, + "showValuesAsPercentage": false, + "legendPosition": "auto", + "legendMode": "table" + } + } + } + } + }, + { + "i": "8db3b8a8-6875-452b-9acc-16695f65a058", + "x": 2, + "y": 0, + "w": 1, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Attacking", + "description": "Damage this type deals to other types", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[typeMatchups]}}", + "name": "typeMatchups", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "direction", + "operation": "equals", + "value": "Attacking" + } + ] + }, + "sort": { + "by": [ + [ + "multiplier", + "desc" + ], + [ + "otherTypeName", + "asc" + ] + ] + } + }, + "scope": { + "scope": "{{scopes.[Pokémon Types]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Pokémon Type]}}" + }, + "variables": [ + "{{variables.[Pokémon Type]}}" + ], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "otherTypeName", + "multiplier", + "effectiveness" + ], + "hiddenColumns": [ + "typeId", + "typeName", + "direction", + "relation", + "otherTypeId" + ], + "columnDisplayNames": { + "otherTypeName": "Target" + }, + "rowLinkColumnName": { + "value": "otherTypeName", + "label": "Other Type" + } + } + } + } + } + }, + { + "i": "4a3b9ada-0ddf-4b89-84d1-0e7910e00a3a", + "x": 3, + "y": 0, + "w": 1, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Defending", + "description": "Damage this type takes from other types", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[typeMatchups]}}", + "name": "typeMatchups", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "direction", + "operation": "equals", + "value": "Defending" + } + ] + }, + "sort": { + "by": [ + [ + "multiplier", + "desc" + ], + [ + "otherTypeName", + "asc" + ] + ] + } + }, + "scope": { + "scope": "{{scopes.[Pokémon Types]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Pokémon Type]}}" + }, + "variables": [ + "{{variables.[Pokémon Type]}}" + ], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "otherTypeName", + "multiplier", + "effectiveness" + ], + "hiddenColumns": [ + "typeId", + "typeName", + "direction", + "relation", + "otherTypeId" + ], + "columnDisplayNames": { + "otherTypeName": "Attacker" + }, + "rowLinkColumnName": { + "value": "otherTypeName", + "label": "Other Type" + } + } + } + } + } + }, + { + "i": "21b1a322-2352-4d0b-ad73-65082a40d408", + "x": 0, + "y": 4, + "w": 4, + "h": 6, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Pokémon", + "description": "", + "timeframe": "none", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[pokemonByType]}}", + "name": "pokemonByType", + "pluginConfigId": "{{configId}}", + "sort": { + "by": [ + [ + "pokedexNumber", + "asc" + ] + ] + } + }, + "scope": { + "scope": "{{scopes.[Pokémon Types]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Pokémon Type]}}" + }, + "variables": [ + "{{variables.[Pokémon Type]}}" + ], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "pokedexNumber", + "pokemonName", + "slot" + ], + "hiddenColumns": [ + "typeId", + "typeName", + "pokemonId" + ], + "rowLinkColumnName": { + "value": "pokemonName", + "label": "Pokémon" + } + } + } + } + } + } + ] + } +} diff --git a/plugins/PokeAPI/v1/defaultContent/scopes.json b/plugins/PokeAPI/v1/defaultContent/scopes.json new file mode 100644 index 00000000..4042f7d9 --- /dev/null +++ b/plugins/PokeAPI/v1/defaultContent/scopes.json @@ -0,0 +1,38 @@ +[ + { + "name": "All Pokémon", + "matches": { + "sourceType": { "type": "oneOf", "values": ["Pokemon"] } + }, + "variable": { + "name": "Pokémon", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + }, + { + "name": "Pokémon Types", + "matches": { + "sourceType": { "type": "oneOf", "values": ["Pokemon Type"] } + }, + "variable": { + "name": "Pokémon Type", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + }, + { + "name": "Generations", + "matches": { + "sourceType": { "type": "oneOf", "values": ["Pokemon Generation"] } + }, + "variable": { + "name": "Generation", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + } +] diff --git a/plugins/PokeAPI/v1/docs/README.md b/plugins/PokeAPI/v1/docs/README.md new file mode 100644 index 00000000..59d20f0a --- /dev/null +++ b/plugins/PokeAPI/v1/docs/README.md @@ -0,0 +1,61 @@ +Explore every Pokémon, battle type and generation in SquaredUp, including base stats, Pokédex entries, type matchups and learnable moves. The data comes from [PokéAPI](https://pokeapi.co), a free and open RESTful Pokémon API ([API documentation](https://pokeapi.co/docs/v2)). + +## Setup + +PokéAPI is free and needs no account, API key or sign-in. + +1. Add the **PokéAPI** data source. +2. Give it a name and save. There is nothing else to configure. + +SquaredUp then imports every Pokémon, type and generation, and installs the out-of-the-box dashboards. + +## Configuration fields + +This plugin has no configuration fields. + +On save, the plugin checks that `https://pokeapi.co` is reachable by fetching the list of types. A failure means SquaredUp (or the agent running the plugin) cannot reach the internet address `pokeapi.co`. + +## What this plugin monitors + +- **Pokémon:** types, height, weight, abilities, base stats and effort value yield, official artwork and cry, plus the moves each Pokémon can learn. +- **Pokédex data:** the latest English Pokédex entry, category, generation, rarity (legendary, mythical or baby), capture rate, gender ratio, egg groups, habitat and what each Pokémon evolves from. +- **Types:** every attacking and defending damage multiplier (the type chart), and which Pokémon have each type as their primary or secondary type. +- **Generations:** each generation's main region and games, the species it introduced, and how many new moves, abilities and types it added. + +The out-of-the-box dashboards include an **Overview** plus a perspective for each **Pokémon**, **Pokémon Type** and **Generation**. + +## Data streams + +- **Pokémon Profile:** types, height (m), weight (kg), base experience, abilities, base stat total, and links to artwork, sprite and cry. One row per Pokémon. +- **Pokémon Stats:** HP, Attack, Defense, Sp. Atk, Sp. Def and Speed, with effort value yield. One row per stat for each Pokémon. +- **Pokémon Species:** Pokédex entry, category, generation, rarity, capture rate, base happiness, gender ratio, egg cycles, growth rate, habitat, colour, shape, egg groups, evolves-from and number of forms. One row per Pokémon. +- **Pokémon Moves:** moves learnable in the most recent game the Pokémon appears in, with learn method (Level Up, Evolution, Machine, Egg or Tutor) and level. One row per move for each Pokémon. +- **Type Matchups:** damage multipliers (2×, ½× or 0×) a type deals to and takes from every other type. One row per matchup for each type. +- **Pokémon by Type:** the Pokémon that have a type, and whether it is their primary or secondary type. Rows link to the Pokémon. +- **Generation Summary:** main region, games, and the number of new species, moves, abilities and types for each generation. +- **Pokémon by Generation:** the species a generation introduced. Rows link to the Pokémon. + +Select several objects on any of these streams to compare them side by side, for example the base stats of two Pokémon or the Pokémon counts of every type. + +The **Pokémon**, **Types** and **Generations** streams are hidden. They build the object list, and **Types** also checks the connection on save. + +## What gets indexed + +| Object type | API source | Represents | +| ----------- | ---------- | ---------- | +| **Pokémon** | `GET /api/v2/pokemon-species` | A species in the National Pokédex. The object ID is its National Pokédex number. | +| **Pokémon Type** | `GET /api/v2/type` | A battle type, such as Fire or Water. Stellar, Unknown and Shadow are skipped because they have no Pokémon. | +| **Generation** | `GET /api/v2/generation` | A game generation, such as Generation I. | + +The data is re-imported every 12 hours, so new species and generations appear automatically once PokéAPI adds them. + +## Known limitations + +- **Species only, no alternate forms.** Mega Evolutions, Gigantamax and regional forms are not indexed and are left out of **Pokémon by Type**. Profiles, stats and moves show each species' default form. +- **Names come from API identifiers.** Object, move, game and ability names are built from PokéAPI's identifiers, so punctuation is missing: "Mr Mime", "Farfetchd", "Lets Go Eevee", "Heartgold Soulsilver". **Pokémon Species** shows the official English Pokémon name, such as "Mr. Mime". +- **Moves cover one game.** **Pokémon Moves** shows the newest game in which the Pokémon has a level-up learnset, which differs between Pokémon (for example Scarlet Violet for Charizard, Legends Arceus for Ponyta). Learnsets from other games are not shown. +- **No evolution chains.** Only the species a Pokémon evolves from is shown. +- **Images are links.** Artwork, sprites and cries are links rather than images inside tiles. +- **English only.** Names, categories and Pokédex entries are in English. +- **No time range.** PokéAPI is static reference data, so no data stream has a timeframe. +- **Read-only.** The plugin only reads public data from PokéAPI, which has no rate limit but asks clients to use it fairly. diff --git a/plugins/PokeAPI/v1/icon.svg b/plugins/PokeAPI/v1/icon.svg new file mode 100644 index 00000000..c1c0242d --- /dev/null +++ b/plugins/PokeAPI/v1/icon.svg @@ -0,0 +1 @@ +pokeapi diff --git a/plugins/PokeAPI/v1/indexDefinitions/default.json b/plugins/PokeAPI/v1/indexDefinitions/default.json new file mode 100644 index 00000000..5cc70be0 --- /dev/null +++ b/plugins/PokeAPI/v1/indexDefinitions/default.json @@ -0,0 +1,34 @@ +{ + "steps": [ + { + "name": "pokemon", + "dataStream": { "name": "pokemon" }, + "timeframe": "none", + "objectMapping": { + "id": "id", + "name": "pokemonName", + "type": { "value": "Pokemon" } + } + }, + { + "name": "types", + "dataStream": { "name": "types" }, + "timeframe": "none", + "objectMapping": { + "id": "id", + "name": "typeName", + "type": { "value": "Pokemon Type" } + } + }, + { + "name": "generations", + "dataStream": { "name": "generations" }, + "timeframe": "none", + "objectMapping": { + "id": "id", + "name": "generationName", + "type": { "value": "Pokemon Generation" } + } + } + ] +} diff --git a/plugins/PokeAPI/v1/metadata.json b/plugins/PokeAPI/v1/metadata.json new file mode 100644 index 00000000..768b8d27 --- /dev/null +++ b/plugins/PokeAPI/v1/metadata.json @@ -0,0 +1,37 @@ +{ + "name": "pokeapi", + "displayName": "PokéAPI", + "version": "1.0.0", + "author": { + "name": "@clarkd", + "type": "community" + }, + "description": "Explore every Pokémon, type and generation from the free PokéAPI, with base stats, Pokédex entries, type matchups and learnable moves.", + "category": "Fun", + "type": "hybrid", + "schemaVersion": "2.1", + "importNotSupported": false, + "restrictedToPlatforms": [], + "keywords": ["pokemon", "pokémon", "pokedex", "pokeapi", "games"], + "objectTypes": ["Pokemon", "Pokemon Type", "Pokemon Generation"], + "links": [ + { + "category": "source", + "url": "https://github.com/squaredup/plugins/tree/main/plugins/PokeAPI/v1", + "label": "Repository" + }, + { + "category": "documentation", + "url": "https://github.com/squaredup/plugins/blob/main/plugins/PokeAPI/v1/docs/README.md", + "label": "Help adding this plugin" + } + ], + "base": { + "plugin": "WebAPI", + "majorVersion": "1", + "config": { + "authMode": "none", + "baseUrl": "https://pokeapi.co/api/v2" + } + } +} diff --git a/plugins/PokeAPI/v1/ui.json b/plugins/PokeAPI/v1/ui.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/plugins/PokeAPI/v1/ui.json @@ -0,0 +1 @@ +[]