diff --git a/.gitignore b/.gitignore index 65ca5ca2..e9d09524 100644 --- a/.gitignore +++ b/.gitignore @@ -125,3 +125,7 @@ static/locales/* # No docker-compose.yml files docker-compose.yml + +# Only keep example templates +/templates/* +!/templates/*.example.ejs \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 4683d6e2..d4fb24cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -466,6 +466,14 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, + "ejs": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.5.tgz", + "integrity": "sha512-dldq3ZfFtgVTJMLjOe+/3sROTzALlL9E34V4/sDtUd/KlBSS0s6U1/+WPE1B4sj9CXHJpL1M6rhNJnc9Wbal9w==", + "requires": { + "jake": "^10.6.1" + } + }, "emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", @@ -808,6 +816,14 @@ "flat-cache": "^2.0.1" } }, + "filelist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.1.tgz", + "integrity": "sha512-8zSK6Nu0DQIC08mUC46sWGXi+q3GGpKydAG36k+JDba6VRpkevvOWUW5a/PhShij4+vHT9M+ghgG7eM+a9JDUQ==", + "requires": { + "minimatch": "^3.0.4" + } + }, "finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", @@ -1081,6 +1097,34 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, + "jake": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz", + "integrity": "sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==", + "requires": { + "async": "0.9.x", + "chalk": "^2.4.2", + "filelist": "^1.0.1", + "minimatch": "^3.0.4" + }, + "dependencies": { + "async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/package.json b/package.json index c2f96b7c..14e01745 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "compression": "^1.7.4", "discord-oauth2": "^2.5.0", "discord.js": "^12.3.1", + "ejs": "^3.1.5", "eslint": "^7.9.0", "express": "^4.17.1", "express-mysql-session": "^2.1.4", diff --git a/src/routes/api.js b/src/routes/api.js index 95ae099d..c6d75683 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -25,6 +25,13 @@ router.post('/search', async (req, res) => { res.json({ data: data }); }); +router.post('/get_template/:name', async (req, res) => { + const template = req.params.name; + const view = req.body.data; + const templateData = await utils.render(template, view); + res.send(templateData); +}); + router.get('/get_settings', async (req, res) => { const data = getSettings(); res.json({ data: data }); diff --git a/src/services/utils.js b/src/services/utils.js index ff80ff3f..eda175ac 100644 --- a/src/services/utils.js +++ b/src/services/utils.js @@ -1,5 +1,10 @@ 'use strict'; +const fs = require('fs'); +const path = require('path'); +const ejs = require('ejs'); + +const TemplatesDir = path.resolve(__dirname, '../../templates'); const config = require('../services/config.js'); const generateString = () => { @@ -46,9 +51,44 @@ const hasRole = (userRoles, requiredRoles) => { const zeroPad = (num, places) => String(num).padStart(places, '0'); +const fileExists = async (path) => { + return new Promise((resolve, reject) => { + try { + fs.exists(path, (exists) => { + resolve(exists); + }); + } catch (e) { + return reject(e); + } + }); +}; + +const render = async (name, data) => { + const filePath = path.resolve(TemplatesDir, name + '.ejs'); + if (!await fileExists(filePath)) { + throw `Template ${filePath} does not exist!`; + } + return new Promise((resolve, reject) => { + try { + ejs.renderFile(filePath, data, (err, str) => { + if (err) { + console.error('Template render error:', err); + //return reject(err); + } + resolve(str); + }); + } catch (e) { + console.error('Template error:', e); + return reject(e); + } + }); +}; + module.exports = { generateString, hasGuild, hasRole, - zeroPad + zeroPad, + fileExists, + render }; diff --git a/src/views/header.mustache b/src/views/header.mustache index 555932fd..fd314899 100644 --- a/src/views/header.mustache +++ b/src/views/header.mustache @@ -28,6 +28,7 @@ + {{#google_analytics_id}} {{/google_analytics_id}} diff --git a/static/js/index.js b/static/js/index.js index 1ba9747d..35d98f1a 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -2729,11 +2729,13 @@ function updateMapTimers () { function getPokemonPopupContent (pokemon) { const despawnDate = new Date(pokemon.expire_timestamp * 1000); + const firstSeenDate = new Date(pokemon.first_seen_timestamp * 1000); + const lastUpdatedDate = new Date(pokemon.updated * 1000); const hasIV = pokemon.atk_iv !== null; - let content = ''; + //let content = ''; let pokemonName; - if (pokemon.form !== 0 && pokemon.form !== null) { + if (pokemon.form > 0) { pokemonName = getFormName(pokemon.form) + ' ' + getPokemonName(pokemon.pokemon_id); } else { pokemonName = getPokemonName(pokemon.pokemon_id); @@ -2741,10 +2743,90 @@ function getPokemonPopupContent (pokemon) { if (pokemon.display_pokemon_id > 0) { pokemonName += ' (' + getPokemonNameNoId(pokemon.display_pokemon_id) + ')'; } - + pokemon.pokemon_name = pokemonName; // TODO: add evolution https://github.com/versx/DataParser/issues/9 const pokemonIcon = getPokemonIcon(pokemon.pokemon_id, pokemon.form, 0, pokemon.gender, pokemon.costume); + pokemon.pokemon_icon = `${availableIconStyles[selectedIconStyle].path}/${pokemonIcon}.png`; + pokemon.icon_path = '/img'; + const pkmn = masterfile.pokemon[pokemon.pokemon_id]; + let pokemonTypes = []; + if (pkmn && pkmn.types && pkmn.types.length > 0) { + const types = pkmn.types; + if (types && types.length > 0) { + if (types.length === 2) { + pokemonTypes.push(types[0].toLowerCase()); + pokemonTypes.push(types[1].toLowerCase()); + } else { + pokemonTypes.push(types[0].toLowerCase()); + } + } + } + pokemon.pokemon_types = pokemonTypes; + pokemon.gender_icon = getGenderIcon(pokemon.gender); + pokemon.has_iv = hasIV; + if (pokemon.size > 0 && pokemon.weight > 0 && (pokemon.pokemon_id === 19 || pokemon.pokemon_id === 129)) { + const baseHeight = pokemon_id === 19 ? 0.300000011920929 : 0.89999998; + const baseWeight = pokemon_id === 19 ? 3.5 : 10; + const size = (size / baseHeight) + (pokemon.weight / baseWeight); + pokemon.size = getSize(size); + } + if (pokemon.move_1 > 0) { + pokemon.move_1_name = getMoveName(pokemon.move_1); + } + if (pokemon.move_2 > 0) { + pokemon.move_2_name = getMoveName(pokemon.move_2); + } + pokemon.enable_scouting = enableScouting; + pokemon.time_utill_despawn = getTimeUntill(despawnDate); + pokemon.time_since_first_seen = getTimeSince(firstSeenDate); + pokemon.time_since_updated = getTimeSince(lastUpdatedDate); + let greatLeagueRankings = []; + if (pokemon.pvp_rankings_ultra_league && pokemon.pvp_rankings_ultra_league.length > 0 && hasRelevantLeagueStats(pokemon.pvp_rankings_ultra_league, false)) { + $.each(pokemon.pvp_rankings_ultra_league, function (index, ranking) { + if (ranking.cp > 0 && ranking.cp >= 2400 && ranking.cp <= 2500 && ranking.rank <= 100) { + let pokemonName; + if (ranking.form !== 0) { + pokemonName = getFormName(ranking.form) + ' ' + getPokemonName(ranking.pokemon); + } else { + pokemonName = getPokemonName(ranking.pokemon); + } + greatLeagueRankings.push({ + pokemon_name: pokemonName, + rank: ranking.rank, + percent: Math.round(ranking.percentage * 1000) / 10, + cp: ranking.cp, + level: ranking.level + }); + } + }); + } + pokemon.great_league_rankings = greatLeagueRankings; + let ultraLeagueRankings = []; + if (pokemon.pvp_rankings_ultra_league && pokemon.pvp_rankings_ultra_league.length > 0 && hasRelevantLeagueStats(pokemon.pvp_rankings_ultra_league, false)) { + $.each(pokemon.pvp_rankings_ultra_league, function (index, ranking) { + if (ranking.cp > 0 && ranking.cp >= 2400 && ranking.cp <= 2500 && ranking.rank <= 100) { + let pokemonName; + if (ranking.form !== 0) { + pokemonName = getFormName(ranking.form) + ' ' + getPokemonName(ranking.pokemon); + } else { + pokemonName = getPokemonName(ranking.pokemon); + } + ultraLeagueRankings.push({ + pokemon_name: pokemonName, + rank: ranking.rank, + percent: Math.round(ranking.percentage * 1000) / 10, + cp: ranking.cp, + level: ranking.level + }); + } + }); + } + pokemon.ultra_league_rankings = ultraLeagueRankings; + + const templateData = getTemplateData('pokemon', pokemon); + return templateData; + /* content += '
' + // START 1ST ROW '
' + @@ -2910,6 +2992,7 @@ function getPokemonPopupContent (pokemon) { ) + '
'; return content; + */ } // eslint-disable-next-line no-unused-vars @@ -2998,7 +3081,52 @@ function getPokestopPopupContent (pokestop) { const lureExpireDate = new Date(pokestop.lure_expire_timestamp * 1000); const invasionExpireDate = new Date(pokestop.incident_expire_timestamp * 1000); const isActiveLure = lureExpireDate >= now; + const isActiveInvasion = invasionExpireDate >= now; + const lastUpdatedDate = new Date(pokestop.updated * 1000); + + pokestop.is_lure_active = isActiveLure; + pokestop.is_invasion_active = isActiveInvasion; + pokestop.lure_time_since = getTimeUntill(lureExpireDate); + pokestop.invasion_time_since = getTimeUntill(invasionExpireDate); + pokestop.lure_expire_time = lureExpireDate.toLocaleTimeString(); + pokestop.invasion_expire_time = invasionExpireDate.toLocaleTimeString(); + pokestop.icon_path = '/img'; + if (isActiveInvasion) { + pokestop.grunt_name = getGruntName(pokestop.grunt_type); + } + if (pokestop.quest_type !== null) { + let questRewards = []; + const conditions = pokestop.quest_conditions; + let conditionsString = ''; + if (conditions !== undefined && conditions.length > 0) { + conditionsString += ' ('; + $.each(conditions, function (index, condition) { + let formatting; + if (index === 0) { + formatting = ''; + } else { + formatting = ', '; + } + + conditionsString += formatting + getQuestCondition(condition); + }); + conditionsString += ')'; + } + //content += 'Quest Condition: ' + getQuestName(pokestop.quest_type, pokestop.quest_target) + conditionsString + '
'; + pokestop.quest_name = getQuestName(pokestop.quest_type, pokestop.quest_target); + pokestop.quest_conditions_formatted = conditionsString; + $.each(pokestop.quest_rewards, function (index, reward) { + //content += 'Quest Reward: ' + getQuestReward(reward) + '
'; + questRewards.push(getQuestReward(reward)); + }); + pokestop.quest_rewards_formatted = questRewards; + } + pokestop.last_updated = lastUpdatedDate; + pokestop.time_since = getTimeSince(lastUpdatedDate); + const templateData = getTemplateData('pokestop', pokestop); + return templateData; + /* let content = '
'; if (pokestop.name === null || pokestop.name === '') { content += '
Unknown Pokestop Name
'; @@ -3073,6 +3201,7 @@ function getPokestopPopupContent (pokestop) { } content += getNavigation(pokestop); return content; + */ } function getPossibleInvasionRewards (pokestop) { @@ -3127,17 +3256,14 @@ function getGymPopupContent (gym) { const now = new Date(); const raidBattleDate = new Date(gym.raid_battle_timestamp * 1000); const raidEndDate = new Date(gym.raid_end_timestamp * 1000); + const updatedDate = new Date(gym.updated * 1000); + const modifiedDate = new Date(gym.last_modified_timestamp * 1000); const isRaid = raidEndDate >= now && parseInt(gym.raid_level) > 0; const isRaidBattle = raidBattleDate <= now && isRaid; + const hasRaidBoss = gym.raid_pokemon_id > 0; - let gymName = ''; - if (gym.name === null || gym.name === '') { - gymName = 'Unknown Gym Name'; - } else { - gymName = gym.name; - } - + let gymName = gym.name ? gym.name : 'Unknown Gym Name'; let titleSize = 16;//'medium'; if (gymName.length > 40) { //titleSize = 'xx-small'; @@ -3150,6 +3276,58 @@ function getGymPopupContent (gym) { titleSize = 14; } + gym.gym_name = gymName; + gym.title_size = titleSize; + gym.is_raid = isRaid; + gym.is_raid_battle = isRaidBattle; + gym.available_slots = gym.availble_slots === 0 ? 'Full' : gym.availble_slots === 6 ? 'Empty' : gym.availble_slots; + gym.has_raid_boss = hasRaidBoss; + let pokemonName = ''; + if (hasRaidBoss && isRaidBattle) { + pokemonName = getPokemonName(gym.raid_pokemon_id); + if (gym.raid_pokemon_form > 0) { + const formName = getFormName(gym.raid_pokemon_form); + pokemonName = formName + ' ' + pokemonName; + gym.form_name = formName; + } + pokemonName += ' ' + getGenderIcon(gym.raid_pokemon_gender); + } else if (isRaidBattle) { + pokemonName = 'Unknown Raid Boss'; + } else { + pokemonName = 'Level ' + gym.raid_level + ' Egg'; + } + let pokemonTypes = []; + if (hasRaidBoss && isRaidBattle) { + const pkmn = masterfile.pokemon[gym.raid_pokemon_id]; + if (pkmn) { + const types = pkmn.types; + if (types && types.length > 0) { + if (types.length === 2) { + pokemonTypes.push(types[0].toLowerCase()); + pokemonTypes.push(types[1].toLowerCase()); + } else { + pokemonTypes.push(types[0].toLowerCase()); + } + } + } + } + gym.pokemon_types = pokemonTypes; + if (hasRaidBoss) { + gym.pokemon_icon = `${availableIconStyles[selectedIconStyle].path}/${getPokemonIcon(gym.raid_pokemon_id, gym.raid_pokemon_form, gym.raid_pokemon_evolution, gym.raid_pokemon_gender, gym.raid_pokemon_costume)}.png`; + } + gym.pokemon_name = pokemonName; + gym.move_1_name = getMoveName(gym.raid_pokemon_move_1); + gym.move_2_name = getMoveName(gym.raid_pokemon_move_2); + gym.guarding_pokemon_name = getPokemonName(gym.guarding_pokemon_id); + gym.team_name = getTeamName(gym.team_id); + gym.icon_path = '/img'; + gym.time_until_battle = getTimeUntill(raidBattleDate); + gym.time_until_end = getTimeUntill(raidEndDate); + gym.time_since_updated = getTimeSince(updatedDate); + gym.time_since_modified = getTimeSince(modifiedDate); + const templateData = getTemplateData('gym', gym); + return templateData; + /* let content = '
' + // START 1ST ROW '
' + @@ -3317,21 +3495,27 @@ function getGymPopupContent (gym) { } content += getNavigation(gym); return content; + */ } function getCellPopupContent (cell) { - let content = '
'; - content += '
Level ' + cell.level + ' S2 Cell
'; - content += 'Id: ' + cell.id + '
'; - - const updatedDate = new Date(cell.updated * 1000); - - content += 'Last Updated: ' + updatedDate.toLocaleTimeString() + ' (' + getTimeSince(updatedDate) + ')'; - content += '
'; + //cell.time_since = getTimeSince(new Date(cell.updated * 1000)); + //const templateData = getTemplateData('cell', cell); + //return templateData; + const content = ` +
+
Level ${cell.level} S2 Cell
+ Id: ${cell.id}
+ Last Updated: ${new Date(cell.updated * 1000).toLocaleTimeString()} (${getTimeSince(new Date(cell.updated * 1000))}) +
+ `; return content; } function getSubmissionTypeCellPopupContent (cell) { + //const templateData = getTemplateData('submission_cell', cell); + //return templateData; + const gymThreshold = [2, 6, 20]; let content = `
Level ${cell.level} S2 Cell
@@ -3340,19 +3524,19 @@ function getSubmissionTypeCellPopupContent (cell) { Pokestop Count: ${cell.count_pokestops}
Gym Count: ${cell.count_gyms}
`; - - const gymThreshold = [2, 6, 20]; if (cell.count_gyms < 3) { - content += 'Submissions until Gym: ' + (gymThreshold[cell.count_gyms] - cell.count); + content += `Submissions untill Gym: ${gymThreshold[cell.count_gyms] - cell.count}`; } else { content += 'Submissions until Gym: Never'; } - - if ((cell.count === 1 && cell.count_gyms < 1) || (cell.count === 5 && cell.count_gyms < 2) || (cell.count === 19 && cell.count_gyms < 3)) { + if ((cell.count === 1 && cell.count_gyms < 1) || + (cell.count === 5 && cell.count_gyms < 2) || + (cell.count === 19 && cell.count_gyms < 3)) { content += '
Next submission will cause a Gym!'; } - - content += '
'; + content += ` + + `; return content; } @@ -3364,47 +3548,19 @@ function degreesToCardinal (d) { } function getWeatherPopupContent (weather) { - const weatherName = weatherTypes[weather.gameplay_condition].name; - const weatherType = weatherTypes[weather.gameplay_condition].types; - const updatedDate = new Date(weather.updated * 1000); - const content = ` -
-
${weatherName}
- Boosted Types:
${weatherType}
- Cell ID: ${weather.id}
- Cell Level: ${weather.level}
- Lat: ${weather.latitude.toFixed(5)}
- Lon: ${weather.longitude.toFixed(5)}
- Gameplay Condition: ${getWeatherName(weather.gameplay_condition)}
- Wind Direction: ${weather.wind_direction}° (${degreesToCardinal(weather.wind_direction)})
- Cloud Level: ${weather.cloud_level}
- Rain Level: ${weather.rain_level}
- Wind Level: ${weather.wind_level}
- Snow Level: ${weather.snow_level}
- Fog Level: ${weather.fog_level}
- Special Effects Level: ${weather.special_effect_level}
- Severity: ${weather.severity}
- Weather Warning: ${weather.warn_weather}

- Last Updated: ${updatedDate.toLocaleTimeString()} (${getTimeSince(updatedDate)}) -
- `; - return content; + weather.weather_name = weatherTypes[weather.gameplay_condition].name; + weather.weather_type = weatherTypes[weather.gameplay_condition].types; + weather.cardinal = degreesToCardinal(weather.wind_direction); + weather.time_since = getTimeSince(new Date(weather.updated * 1000)); + const templateData = getTemplateData('weather', weather); + return templateData; } function getNestPopupContent(nest) { - const lastUpdated = new Date(nest.updated * 1000); - const pokemonName = getPokemonName(nest.pokemon_id); - const content = ` -
-
Park: ${nest.name}
- Pokemon: ${pokemonName}
- Average: ${nest.pokemon_avg.toLocaleString()}
- Count: ${nest.pokemon_count.toLocaleString()}
-
-
Last Updated: ${lastUpdated.toLocaleString()}

-
- `; - return content; + nest.pokemon_name = getPokemonName(nest.pokemon_id); + nest.last_updated = new Date(nest.updated * 1000); + const templateData = getTemplateData('nest', nest); + return templateData; } function getPortalPopupContent(portal) { @@ -3425,13 +3581,8 @@ function getPortalPopupContent(portal) { } function getScanAreaPopupContent(name, size) { - let content = ` -
-
Area: ${name}
- Size: ${size} km2 -
- `; - return content; + const templateData = getTemplateData('scanarea', { name, size }); + return templateData; } function getNavigation(data) { @@ -4065,6 +4216,10 @@ function getSpawnpointMarker (spawnpoint, ts) { const timer = Math.round(spawnpoint.despawn_second / 60); content += '
Despawn Timer: ' + timer + ' minutes'; } + /* + const hasTimer = spawnpoint.despawn_second !== null; + const templateData = getTemplateData('spawnpoint', spawnpoint); + */ const circle = L.circle([spawnpoint.lat, spawnpoint.lon], { color: hasTimer ? 'green' : 'red', fillColor: hasTimer ? 'green' : 'red', @@ -4188,15 +4343,8 @@ function getDeviceMarker (device, ts) { } function getDevicePopupContent (device) { - const lastSeenDate = new Date(device.last_seen * 1000); - const lastSeen = lastSeenDate.toLocaleTimeString() + ' (' + getTimeSince(lastSeenDate) + ')'; - const ts = Math.round((new Date()).getTime() / 1000); - const isOffline = isDeviceOffline(device, ts); - const content = '
' + device.uuid + '

' + - 'Instance: ' + device.instance_name + '
' + - 'Last Seen: ' + lastSeen + '
' + - 'Status: ' + (isOffline ? 'Offline' : 'Online'); - return content; + const data = getTemplateData('device', device); + return data; } function isDeviceOffline (device, ts) { @@ -4261,6 +4409,32 @@ function setDespawnTimer (marker) { } } +function getTemplateData(template, data) { + const { marker, ...dataWithoutMarker } = data; + let results = null; + $.ajax({ + url: '/api/get_template/' + template, + type: 'POST', + data: JSON.stringify({ + data: dataWithoutMarker, + _csrf: '{{csrf}}' + }), + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + async: false, + success: function (result) { + //console.log('result:', result); + results = result; + }, + failure: function (err) { + console.error('Error:', err); + } + }); + return results; +} + // MARK: - Misc diff --git a/templates/cell.example.ejs b/templates/cell.example.ejs new file mode 100644 index 00000000..8be74e6f --- /dev/null +++ b/templates/cell.example.ejs @@ -0,0 +1,5 @@ +
+
Level <%= level %> S2 Cell
+Id: <%= id %>
+Last Updated: <%= new Date(updated * 1000).toLocaleTimeString() %> (<%= time_since %>) +
\ No newline at end of file diff --git a/templates/device.example.ejs b/templates/device.example.ejs new file mode 100644 index 00000000..663dced7 --- /dev/null +++ b/templates/device.example.ejs @@ -0,0 +1,26 @@ +<% +function getTimeSince (date) { + const diff = Math.round((new Date() - date) / 1000); + const h = Math.floor(diff / 3600); + const m = Math.floor(diff % 3600 / 60); + const s = Math.floor(diff % 3600 % 60); + let str; + if (h > 0) { + str = h + 'h ' + m + 'm ' + s + 's'; + } else if (m > 0) { + str = m + 'm ' + s + 's'; + } else { + str = s + 's'; + } + return str; +} +%> + +
+
<%= uuid %>
+
+
+Instance: <%= instance_name %>
+<% let lastSeenDate = new Date(last_seen * 1000); %> +Last Seen: <%= lastSeenDate.toLocaleTimeString() + ' (' + getTimeSince(lastSeenDate) + ')'; %>
+Status: <%= last_seen > (Math.round(new Date().getTime() / 1000) - 15 * 60) ? 'Online' : 'Offline' %> \ No newline at end of file diff --git a/templates/gym.example.ejs b/templates/gym.example.ejs new file mode 100644 index 00000000..b49031c2 --- /dev/null +++ b/templates/gym.example.ejs @@ -0,0 +1,133 @@ +
+
+ <%= gym_name %> +
+
+
+ +
+
+
+ +<% if (is_raid) { %> +
+
+
+ <% if (has_raid_boss && is_raid_battle) { %> + + <% } else { %> + + <% } %> +
+
+ <% if (has_raid_boss && is_raid_battle) { %> + <% if (pokemon_types && pokemon_types.length > 0) { %> +
+ <% pokemon_types.forEach(function(type) { %> +   + <% }); %> +
+ <% } %> + <% } %> +
+
+
+ <%= pokemon_name %>
+ <% if (has_raid_boss && is_raid_battle) { %> + <% if (raid_pokemon_cp) { %> + <% if (raid_is_exclusive) { %> + Level: EX
+ <% } else { %> + Level: <%= raid_level %>
+ <% } %> + <% } %> + <% if (raid_pokemon_move_1) { %> + Fast: <%= move_1_name %>
+ <% } %> + <% if (raid_pokemon_move_2) { %> + Charge: <%= move_2_name %>
+ <% } %> + <% if (in_battle) { %> + Gym last seen in battle!
+ <% } %> + <% if (raid_pokemon_form > 0) { %> + Form: <%= form_name %>
+ <% } %> + <% } %> + <% if (ex_raid_eligible) { %> + + <% } %> +
+
+
+<% } else { %> +
+ <% if (url) { + let teamClass = team_id === 0 + ? 'team-neutral' + : team_id === 1 + ? 'team-mystic' + : team_id === 2 + ? 'team-valor' + : team_id === 3 + ? 'team-instinct' + : ''; + url.replace('http://', 'https://'); + %> +
+ +
+ <% } %> +
+ Team: <%= team_name %>
+ Slots Available: <%= available_slots %>
+ <% if (guarding_pokemon_id > 0) { %> + Guard: <%= guarding_pokemon_name %>
+ <% } %> + <% if (total_cp > 0) { %> + Total CP: <%= total_cp.toLocaleString() %>
+ <% } %> + <% if (in_battle > 0) { %> + Gym is under attack!
+ <% } %> + <% if (ex_raid_eligible > 0) { %> + + <% } %> +
+
+
+<% } %> + +
+ <% if (is_raid && !is_raid_battle) { %> + Raid Start: <%= new Date(raid_battle_timestamp * 1000).toLocaleString() %> (<%= time_until_battle %>)
+ <% } %> + <% if (is_raid) { %> + Raid End: <%= new Date(raid_end_timestamp * 1000).toLocaleString() %> (<%= time_until_end %>)

+ <% } %> +
+ <% if (updated) { %> %> + Last Updated: <%= new Date(updated * 1000).toLocaleString() %> (<%= time_since_updated %>)
+ <% } %> + <% if (last_modified_timestamp) { %> + Last Modified: <%= new Date(last_modified_timestamp * 1000).toLocaleString() %> (<%= time_since_modified %>)
+ <% } %> + +
+
+
+ + + +
+
+ + + +
+
+ + + +
+
\ No newline at end of file diff --git a/templates/nest.example.ejs b/templates/nest.example.ejs new file mode 100644 index 00000000..a1d58351 --- /dev/null +++ b/templates/nest.example.ejs @@ -0,0 +1,8 @@ +
+
Park: <%= name %>
+ Pokemon: <%= pokemon_name %>
+ Average: <%= pokemon_avg.toLocaleString() %>
+ Count: <%= pokemon_count.toLocaleString() %>
+
+ Last Updated: <%= last_updated.toLocaleString() %>
+
\ No newline at end of file diff --git a/templates/pokemon.example.ejs b/templates/pokemon.example.ejs new file mode 100644 index 00000000..6421be20 --- /dev/null +++ b/templates/pokemon.example.ejs @@ -0,0 +1,126 @@ +
+
+
<%= pokemon_name %> <%= gender_icon %>
+
+
+
+ <% if (!(display_pokemon_id > 0) && weather > 0) { %> + + <% } %> +
+
+
+ +
+
+
+ +
+
+ <% if (pokemon_types && pokemon_types.length > 0) { %> + <% pokemon_types.forEach(function(type) { %> +   + <% }); %> + <% } %> +
+
+
+ <% if (has_iv) { %> + <% const ivPercent = Math.round((atk_iv + def_iv + sta_iv) / 45 * 1000) / 10; %> + IV: <%= ivPercent %>% (A<%= atk_iv %>|D<%= def_iv %>|S<%= sta_iv%>)
+ <% } %> + <% if (cp) { %> + CP: <%= cp %> (Lvl. <%= level %>)
+ <% } %> + <% if (move_1) { %> + Fast: <%= move_1_name %>
+ <% } %> + <% if (move_2) { %> + Charge: <%= move_2_name %>
+ <% } %> + <% if (size > 0 && weight > 0 && (pokemon_id === 19 || pokemon_id === 129)) { %> + Size: <%= size %> | Weight: <%= Math.round(weight) %>kg
+ <% } else if (weight > 0) { %> + Weight: <%= Math.round(weight) %>kg
+ <% } %> + <% if (capture_1 && capture_2 && capture_3) { %> + Catch Chances:
+ <%= (capture_1 * 100).toFixed(1) %>% + <%= (capture_2 * 100).toFixed(1) %>%
+ <%= (capture_3 * 100).toFixed(1) %>%
+ <% } %> +
+
+
+
+
+ <% if (expire_timestamp_verified) { %> + Despawn Time: + <% } else { %> + Despawn Time: ~ + <% } %> + <%= new Date(expire_timestamp * 1000).toLocaleString() %> (<%= time_utill_despawn %>)
+
+
+ <% if (first_seen_timestamp > 0) { %> + <% const firstSeenDate = new Date(first_seen_timestamp * 1000); %> + First Seen: <%= firstSeenDate.toLocaleString() %> (<%= time_since_first_seen %>)
+ <% } %> +
+
+ <% if (updated > 0) { %> + <% const updatedDate = new Date(updated * 1000); %> + Latest Seen: <%= updatedDate.toLocaleString() %> (<%= time_since_updated %>)
+ <% } %> + + <% if (great_league_rankings && great_league_rankings.length > 0) { %> +
+ Great League:
+ <% for (let i = 0; i < great_league_rankings.length; i++) { %> + <% let ranking = great_league_rankings[i]; %> + <%= ranking.pokemon_name %>: #<%= ranking.rank %> (<%= ranking.percent %>%) @ <%= ranking.cp %> CP (Lvl. <%= ranking.level %>)
+ <% } %> + <% } %> + <% if (ultra_league_rankings && ultra_league_rankings.length > 0) { %> +
+ Ultra League:
+ <% for (let i = 0; i < ultra_league_rankings.length; i++) { %> + <% let ranking = ultra_league_rankings[i]; %> + <%= ranking.pokemon_name %>: #<%= ranking.rank %> (<%= ranking.percent %>%) @ <%= ranking.cp %> CP (Lvl. <%= ranking.level %>)
+ <% } %> + <% } %> + +
+
+
+
+ [Hide]  + [Exclude] +
+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+ <% if (enable_scouting) { %> +
+ + <% } %> +
\ No newline at end of file diff --git a/templates/pokestop.example.ejs b/templates/pokestop.example.ejs new file mode 100644 index 00000000..58ea3915 --- /dev/null +++ b/templates/pokestop.example.ejs @@ -0,0 +1,75 @@ +
+ <% if (name) { %> +
<%= name %>
+ <% } else { %> +
Unknown Pokestop Name
+ <% } %> + + <% if (url) { + let lureClass = is_lure_active && lure_id !== 0 + ? (lure_id === 501 + ? 'lure-normal' + : lure_id === 502 + ? 'lure-glacial' + : lure_id === 503 + ? 'lure-mossy' + : lure_id === 504 + ? 'lure-magnetic' + : 'lure-normal') + : ''; + %> +

+ <% } %> + +
+
+ + <% if (is_lure_active) { %> + Lure Type: <%= lure_name %>
+ Lure End Time: <%= lure_expire_time %> (<%= lure_time_since %>)

+ <% } %> + + <% if (is_invasion_active) { %> + Team Rocket Invasion
+ Grunt Type: <%= grunt_name %>
+ End Time: <%= invasion_expire_time %> (<%= invasion_time_since %>)

+ <% } %> + + <% if (quest_type) { %> + Quest Condition: <%= quest_name %> <% if (quest_conditions_formatted) { %><%= quest_conditions_formatted %><% } %>
+ <% quest_rewards_formatted.forEach(function(reward) { %> + Quest Reward: <%= reward %>
+ <% }); %> +
+ <% } %> + + <% if (updated) { %> + Last Updated: <%= new Date(updated * 1000).toLocaleString() %> (<%= time_since %>)
+ <% } %> + +
+ <% if (quest_type) { %> + <% let questReward = quest_rewards ? quest_rewards[0] : {} %> +
[Exclude]
+ <% } %> + +
+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
\ No newline at end of file diff --git a/templates/scanarea.example.ejs b/templates/scanarea.example.ejs new file mode 100644 index 00000000..3bb05a74 --- /dev/null +++ b/templates/scanarea.example.ejs @@ -0,0 +1,4 @@ +
+
Area: <%= name %>
+ Size: <%= size %> km2 +
\ No newline at end of file diff --git a/templates/spawnpoint.example.ejs b/templates/spawnpoint.example.ejs new file mode 100644 index 00000000..72248ad9 --- /dev/null +++ b/templates/spawnpoint.example.ejs @@ -0,0 +1,4 @@ +
Spawnpoint
+<% if (despawn_second) { %> +
Despawn Timer: <%= Math.round(despawn_second / 60) %> minutes +<% } %> \ No newline at end of file diff --git a/templates/submission_cell.example.ejs b/templates/submission_cell.example.ejs new file mode 100644 index 00000000..0239cb20 --- /dev/null +++ b/templates/submission_cell.example.ejs @@ -0,0 +1,17 @@ +
+
Level <%= level %> S2 Cell
+Id: <%= id %>
+Total Count: <%= count %>
+Pokestop Count: <%= count_pokestops %>
+Gym Count: <%= count_gyms %>
+ +<% const gymThreshold = [2, 6, 20]; %> +<% if (count_gyms < 3) { %> + Submissions untill Gym: <%= gymThreshold[count_gyms] - count %> +<% } else { %> + Submissions untill Gym: Never +<% } %> +<% if ((count === 1 && count_gyms < 1) || (count === 5 && count_gyms < 2) || (count === 19 && count_gyms < 3)) { %> +
Next submission will cause a Gym! +<% } %> +
\ No newline at end of file diff --git a/templates/weather.example.ejs b/templates/weather.example.ejs new file mode 100644 index 00000000..0bb11a49 --- /dev/null +++ b/templates/weather.example.ejs @@ -0,0 +1,19 @@ +
+
<%= weather_name %>
+Boosted Types:
<%= weather_type %>
+Cell ID: <%= id %>
+Cell Level: <%= level %>
+Lat: <%= latitude.toFixed(5) %>
+Lon: <%= longitude.toFixed(5) %>
+Gameplay Condition: <%= gameplay_condition %>
+Wind Direction: <%= wind_direction %>° (<%= cardinal %>)
+Cloud Level: <%= cloud_level %>
+Rain Level: <%= rain_level %>
+Wind Level: <%= wind_level %>
+Snow Level: <%= snow_level %>
+Fog Level: <%= fog_level %>
+Special Effects Level: <%= special_effect_level %>
+Severity: <%= severity %>
+Weather Warning: <%= warn_weather %>

+Last Updated: <%= new Date(updated * 1000).toLocaleTimeString() %> (<%= time_since %>) +
\ No newline at end of file