diff --git a/locale/de.json b/locale/de.json index 83fe223d8..10bb3a0a2 100644 --- a/locale/de.json +++ b/locale/de.json @@ -37,6 +37,7 @@ "Attack":"Angriff", "Autumn":"Herbst", "Available forms": "verfügbare Formen", + "Average": "Average", "Black":"Schwarz", "Battle a Team Leader 2 times":"Kämpfe 2 mal gegen einen Team Rocket Leader", "Battle Another Trainer 2 times":"Kämpfe 2 mal gegen einen anderen Trainer", @@ -323,6 +324,7 @@ "Land":"Land", "Land 3 throws":"Lande 3 Würfe", "language":"Sprache", + "Large": "Large", "Legendary":"Legendär", "level":"Level", "Level 1 Shadow":"Crypto Level 1", @@ -521,6 +523,7 @@ "size":"Größe", "Sky":"Zenit", "slot changes":"Platzänderung", + "Small": "Small", "snow":"Schnee", "Snowy":"Schneeform", "Something went wrong with your request":"Bei deiner Anfrage ist ein Fehler aufgetreten", @@ -546,6 +549,7 @@ "Sun Stone":"Sonnenstein", "Sunny":"Sonnenform", "sunny":"Sonnig", + "Super": "Super", "Super Effective Against":"sehr effektiv gegen", "Super Potion":"Supertrank", "Take 3 Snapshots":"Mache 3 Fotos", diff --git a/package.json b/package.json index 55503a796..2034bf652 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "poracle", - "version": "4.6.2", + "version": "4.7.0", "description": "Webhook processing and personalised discord|telegram alarms", "keywords": [ "poracle", @@ -41,13 +41,14 @@ "deep-object-diff": "^1.1.9", "discord.js": "^13.16.0", "fast-json-stable-stringify": "^2.1.0", - "fastify": "^4.23.0", + "fastify": "^4.25.2", "flat-cache": "^3.0.4", "form-data": "^4.0.0", "geo-tz": "^7.0.7", "handlebars": "^4.7.7", "hastebin-gen": "^2.0.5", "import-fresh": "^3.3.0", + "json5": "^2.2.3", "knex": "^2.0.0", "moment": "^2.29.4", "moment-precise-range-plugin": "^1.3.0", @@ -60,6 +61,7 @@ "nodes2ts": "^3.0.0", "ohbem": "^1.5.1", "point-in-polygon": "^1.1.0", + "rbush": "^3.0.1", "readline-sync": "^1.4.10", "s2-geometry": "^1.2.10", "strip-json-comments": "^3.1.1", diff --git a/src/app.js b/src/app.js index 125a1eae2..b06df49e8 100644 --- a/src/app.js +++ b/src/app.js @@ -538,7 +538,7 @@ async function processOne(hook) { fastify.controllerLog.debug(`${hook.message.encounter_id}: Wild encounter was received but set to be ignored in config`) break } - if (!hook.message.poracleTest) { + if (!hook.message.poracleTest && !config.tuning?.disablePokemonCache) { fastify.webhooks.info(`pokemon ${JSON.stringify(hook.message)}`) const verifiedSpawnTime = (hook.message.verified || hook.message.disappear_time_verified) const cacheKey = `${hook.message.encounter_id}${verifiedSpawnTime ? 'T' : 'F'}${hook.message.cp}` @@ -902,8 +902,12 @@ async function run() { setTimeout(processPogoEvents, 30000) setTimeout(processPossibleShiny, 30000) - let watchGeofence = Array.isArray(config.geofence.path) ? config.geofence.path : [config.geofence.path] - watchGeofence = watchGeofence.map((x) => path.join(__dirname, `../${x}`)) + let watchGeofence = Array.isArray(config.geofence.path) + ? config.geofence.path + : [config.geofence.path] + watchGeofence = watchGeofence.map((x) => (x.startsWith('http') + ? path.join(__dirname, '../.cache', `${x.replace(/\//g, '__')}.json`) + : path.join(__dirname, `../${x}`))) chokidar.watch(watchGeofence, { awaitWriteFinish: true, @@ -919,7 +923,8 @@ async function run() { // This splice mechanism replaces array in place (relies on no caching) const newGeofence = require('./lib/geofenceLoader').readAllGeofenceFiles(config) - geofence.splice(0, geofence.length, ...newGeofence) + geofence.rbush = newGeofence.rbush + geofence.geofence = newGeofence.geofence } catch (err) { log.error('Error reloading dts', err) } diff --git a/src/controllers/controller.js b/src/controllers/controller.js index 336f8e555..6df472859 100644 --- a/src/controllers/controller.js +++ b/src/controllers/controller.js @@ -390,9 +390,20 @@ class Controller extends EventEmitter { } pointInArea(point) { - if (!this.geofence.length) return [] + if (!this.geofence.geofence.length) return [] + + const result = this.geofence.rbush.search({ + minX: point[0], + minY: point[1], + maxX: point[0], + maxY: point[1], + }) + const matchAreas = [] - for (const areaObj of this.geofence) { + + for (const potential of result) { + const areaObj = potential.fence + if (areaObj.path) { if (inside(point, areaObj.path)) { matchAreas.push({ @@ -416,7 +427,15 @@ class Controller extends EventEmitter { } } } - return matchAreas + + const dedupedList = [] + + for (const match of matchAreas) { + if (!dedupedList.some((x) => x.name === match.name)) { + dedupedList.push(match) + } + } + return dedupedList } // database methods below diff --git a/src/controllers/fortupdate.js b/src/controllers/fortupdate.js index 5f2c2b05b..4a9a783a2 100644 --- a/src/controllers/fortupdate.js +++ b/src/controllers/fortupdate.js @@ -3,7 +3,7 @@ const moment = require('moment-timezone') const Controller = require('./controller') /** - * Controller for processing nest webhooks + * Controller for processing fort update webhooks */ class FortUpdate extends Controller { async fortUpdateWhoCares(obj) { @@ -47,7 +47,7 @@ class FortUpdate extends Controller { // group by humans.id, humans.name, humans.type, humans.language, humans.latitude, humans.longitude, invasion.template, invasion.distance, invasion.clean, invasion.ping } else { query = query.concat(` - and ((nests.distance = 0 and (${areastring})) or nests.distance > 0) + and ((forts.distance = 0 and (${areastring})) or forts.distance > 0) `) // group by humans.id, humans.name, humans.type, humans.language, humans.latitude, humans.longitude, invasion.template, invasion.distance, invasion.clean, invasion.ping } @@ -96,23 +96,17 @@ class FortUpdate extends Controller { } data.name = this.escapeJsonString(data.name) - const nestExpiration = data.reset_time + (7 * 24 * 60 * 60) - data.tth = moment.preciseDiff(Date.now(), nestExpiration * 1000, true) - data.disappearDate = moment(nestExpiration * 1000).tz(geoTz.find(data.latitude, data.longitude)[0].toString()).format(this.config.locale.date) + const expiration = data.reset_time + (7 * 24 * 60 * 60) + data.tth = moment.preciseDiff(Date.now(), expiration * 1000, true) + data.disappearDate = moment(expiration * 1000).tz(geoTz.find(data.latitude, data.longitude)[0].toString()).format(this.config.locale.date) data.resetDate = moment(data.reset_time * 1000).tz(geoTz.find(data.latitude, data.longitude)[0].toString()).format(this.config.locale.date) - data.disappearTime = moment(nestExpiration * 1000).tz(geoTz.find(data.latitude, data.longitude)[0].toString()).format(this.config.locale.time) + data.disappearTime = moment(expiration * 1000).tz(geoTz.find(data.latitude, data.longitude)[0].toString()).format(this.config.locale.time) data.resetTime = moment(data.reset_time * 1000).tz(geoTz.find(data.latitude, data.longitude)[0].toString()).format(this.config.locale.time) data.applemap = data.appleMapUrl // deprecated data.mapurl = data.googleMapUrl // deprecated data.distime = data.disappearTime // deprecated - // Stop handling if it already disappeared or is about to go away - // if (data.tth.firstDateWasLater || ((data.tth.days * 24 * 3600) + (data.tth.hours * 3600) + (data.tth.minutes * 60) + data.tth.seconds) < minTth) { - // this.log.debug(`${data.pokestop_id} Nest already disappeared or is about to go away in: ${data.tth.days}d ${data.tth.hours}:${data.tth.minutes}:${data.tth.seconds}`) - // return [] - // } - data.matchedAreas = this.pointInArea([data.latitude, data.longitude]) data.matched = data.matchedAreas.map((x) => x.name.toLowerCase()) @@ -212,14 +206,12 @@ class FortUpdate extends Controller { if (discordCacheBad) { whoCares.forEach((cares) => { - this.log.verbose(`${logReference}: Not creating nest alert (Rate limit) for ${cares.type} ${cares.id} ${cares.name} ${cares.language} ${cares.template}`) + this.log.verbose(`${logReference}: Not creating fort update alert (Rate limit) for ${cares.type} ${cares.id} ${cares.name} ${cares.language} ${cares.template}`) }) return [] } - data.shinyPossible = this.shinyPossible.isShinyPossible(data.pokemonId, data.formId) - data.stickerUrl = data.imgUrl const geoResult = await this.getAddress({ lat: data.latitude, lon: data.longitude }) @@ -260,16 +252,17 @@ class FortUpdate extends Controller { let [platform] = cares.type.split(':') if (platform === 'webhook') platform = 'discord' + const now = new Date() + const time = moment.tz(now, this.config.locale.time, geoTz.find(data.latitude, data.longitude)[0].toString()) const view = { ...geoResult, ...data, - time: data.distime, tthd: data.tth.days, tthh: data.tth.hours, tthm: data.tth.minutes, tths: data.tth.seconds, - now: new Date(), - nowISO: new Date().toISOString(), + time: time.format(this.config.locale.time), + nowISO: now.toISOString(), areas: data.matchedAreas.filter((area) => area.displayInMatches).map((area) => area.name).join(', '), } diff --git a/src/controllers/query.js b/src/controllers/query.js index bf7e94ec4..3edcf9cdd 100644 --- a/src/controllers/query.js +++ b/src/controllers/query.js @@ -60,10 +60,12 @@ class Query { } pointInArea(point) { - if (!this.geofence.length) return [] + const { geofence } = this.geofence + + if (!geofence.length) return [] const matchAreas = [] - for (const areaObj of this.geofence) { + for (const areaObj of geofence) { if (areaObj.path && inside(point, areaObj.path)) matchAreas.push(areaObj.name.toLowerCase()) if (areaObj.multipath) { for (const p of areaObj.multipath) { diff --git a/src/lib/configFetcher.js b/src/lib/configFetcher.js index d2ea20401..cef0f04f1 100644 --- a/src/lib/configFetcher.js +++ b/src/lib/configFetcher.js @@ -77,7 +77,7 @@ module.exports = { if (performChecks) { configChecker.checkConfig(config) configChecker.checkDts(dts, config) - configChecker.checkGeofence(geofence) + configChecker.checkGeofence(geofence.geofence) } moment.locale(config.locale.timeformat) diff --git a/src/lib/discord/commando/commands/channel.js b/src/lib/discord/commando/commands/channel.js index 6bd1dad1d..251d43761 100644 --- a/src/lib/discord/commando/commands/channel.js +++ b/src/lib/discord/commando/commands/channel.js @@ -19,7 +19,7 @@ exports.run = async (client, msg, [args]) => { [,, areaName] = areaName.match(client.re.areaRe) areaName = areaName.toLowerCase() } - const confAreas = client.geofence.map((area) => area.name.toLowerCase()).sort() + const confAreas = client.geofence.geofence.map((area) => area.name.toLowerCase()).sort() const isValidArea = confAreas.filter((x) => areaName === x) if (!isValidArea.length) { areaName = '' diff --git a/src/lib/geofenceLoader.js b/src/lib/geofenceLoader.js index 4b94eee3a..6e031eb7e 100644 --- a/src/lib/geofenceLoader.js +++ b/src/lib/geofenceLoader.js @@ -1,6 +1,7 @@ const stripJsonComments = require('strip-json-comments') const fs = require('fs') const path = require('path') +const RBush = require('rbush') const { log } = require('./logger') function getGeofenceFromGEOjson(config, rawdata) { @@ -74,6 +75,29 @@ function readGeofenceFile(config, filename) { return geofence } +function getBoundingBox(fencePath) { + let minX = Number.MAX_VALUE; let minY = Number.MAX_VALUE; let maxX = Number.MIN_VALUE; let maxY = Number.MIN_VALUE + + for (const point of fencePath) { + if (point[0] < minX) { + [minX] = point + } + if (point[0] > maxX) { + [maxX] = point + } + if (point[1] < minY) { + [, minY] = point + } + if (point[1] > maxY) { + [, maxY] = point + } + } + + return { + minX, minY, maxX, maxY, + } +} + function readAllGeofenceFiles(config) { const fencePaths = Array.isArray(config.geofence.path) ? config.geofence.path : [config.geofence.path] const geofence = fencePaths.flatMap((fencePath) => readGeofenceFile( @@ -82,7 +106,31 @@ function readAllGeofenceFiles(config) { ? path.resolve(__dirname, '../../.cache', `${fencePath.replace(/\//g, '__')}.json`) : path.join(__dirname, `../../${fencePath}`), )) - return geofence + + const tree = new RBush() + + for (const areaObj of geofence) { + if (areaObj.path && areaObj.path.length > 0) { + tree.insert({ + ...getBoundingBox(areaObj.path), + fence: areaObj, + }) + } else if (areaObj.multipath) { + for (const p of areaObj.multipath) { + if (p.length > 0) { + tree.insert({ + ...getBoundingBox(p), + fence: areaObj, + }) + } + } + } + } + + return { + rbush: tree, + geofence, + } } module.exports = { readAllGeofenceFiles } \ No newline at end of file diff --git a/src/lib/poracleMessage/commands/area.js b/src/lib/poracleMessage/commands/area.js index 3cbd72418..310f4ee9d 100644 --- a/src/lib/poracleMessage/commands/area.js +++ b/src/lib/poracleMessage/commands/area.js @@ -26,7 +26,7 @@ exports.run = async (client, msg, args, options) => { return msg.reply(translator.translate('You do not have permission to execute this command')) } - let selectableGeofence = client.geofence + let selectableGeofence = client.geofence.geofence // Note for Poracle admins we don't remove the userSelectable items // But we do apply the filtering later based on the user/channel that is the target (targetIsAdmin used instead) if (!msg.isFromAdmin) selectableGeofence = selectableGeofence.filter((area) => area.userSelectable ?? true) @@ -102,7 +102,7 @@ exports.run = async (client, msg, args, options) => { if (areasNotAlreadyInList.length) { await msg.reply(`${translator.translate('Added areas:')} ${areasNotAlreadyInList.map((x) => x.name).join(', ')}`) } - await msg.reply(trackedCommand.currentAreaText(translator, client.geofence, uniqueNewAreas)) + await msg.reply(trackedCommand.currentAreaText(translator, client.geofence.geofence, uniqueNewAreas)) await client.query.updateQuery('profiles', { area: JSON.stringify(uniqueNewAreas) }, { id: target.id, @@ -136,7 +136,7 @@ exports.run = async (client, msg, args, options) => { if (removeAreasPresent.length) { await msg.reply(`${translator.translate('Removed areas:')} ${removeAreasPresent.map((x) => x.name).join(', ')}`) } - await msg.reply(trackedCommand.currentAreaText(translator, client.geofence, uniqueNewAreas)) + await msg.reply(trackedCommand.currentAreaText(translator, client.geofence.geofence, uniqueNewAreas)) await client.query.updateQuery('profiles', { area: JSON.stringify(uniqueNewAreas) }, { id: target.id, profile_no: currentProfileNo }) break @@ -176,7 +176,7 @@ exports.run = async (client, msg, args, options) => { case 'overview': { if (client.config.geocoding.staticProvider.toLowerCase() === 'tileservercache') { const staticMap = await geofenceTileGenerator.generateGeofenceOverviewTile( - client.geofence, + client.geofence.geofence, client.query.tileserverPregen, args.length >= 2 ? args : JSON.parse(human.area), ) @@ -210,7 +210,7 @@ exports.run = async (client, msg, args, options) => { } } else { staticMap = await geofenceTileGenerator.generateGeofenceTile( - client.geofence, + client.geofence.geofence, client.query.tileserverPregen, area, ) @@ -228,7 +228,7 @@ exports.run = async (client, msg, args, options) => { break } default: { - await msg.reply(trackedCommand.currentAreaText(translator, client.geofence, JSON.parse(human.area))) + await msg.reply(trackedCommand.currentAreaText(translator, client.geofence.geofence, JSON.parse(human.area))) await msg.reply( translator.translateFormat('Valid commands are `{0}area list`, `{0}area add `, `{0}area remove `', util.prefix), diff --git a/src/lib/poracleMessage/commands/tracked.js b/src/lib/poracleMessage/commands/tracked.js index 473b71f23..e4dfa19f0 100644 --- a/src/lib/poracleMessage/commands/tracked.js +++ b/src/lib/poracleMessage/commands/tracked.js @@ -246,7 +246,7 @@ exports.run = async (client, msg, args, options) => { const maplink = `https://maps.google.com/maps?q=${human.latitude},${human.longitude}` if (args.includes('area')) { - return msg.reply(currentAreaText(translator, client.geofence, JSON.parse(human.area))) + return msg.reply(currentAreaText(translator, client.geofence.geofence, JSON.parse(human.area))) } let message = '' @@ -267,7 +267,7 @@ exports.run = async (client, msg, args, options) => { } await msg.reply(`${adminExplanation}${translator.translate('Your alerts are currently')} **${human.enabled ? `${translator.translate('enabled')}` : `${translator.translate('disabled')}`}**${restartExplanation}${locationText}`, { style: 'markdown' }) - message = message.concat('\n\n', currentAreaText(translator, client.geofence, JSON.parse(human.area))) + message = message.concat('\n\n', currentAreaText(translator, client.geofence.geofence, JSON.parse(human.area))) if (profile) { message = message.concat('\n\n', `${translator.translate('Your profile is currently set to:')} ${profile.name}`) diff --git a/src/lib/shinyLoader.js b/src/lib/shinyLoader.js index 23f908f49..b798d93df 100644 --- a/src/lib/shinyLoader.js +++ b/src/lib/shinyLoader.js @@ -54,7 +54,7 @@ class ShinyPossible { try { if (!this.shinyPossibleMap.map) return false - if (formId && `${pokemonId}_${formId}` in this.shinyPossibleMap.map) return true + if (`${pokemonId}_${formId}` in this.shinyPossibleMap.map) return true if (pokemonId in this.shinyPossibleMap.map) return true return false diff --git a/src/routes/apiGeofence.js b/src/routes/apiGeofence.js index c8cda3f91..f3a373494 100644 --- a/src/routes/apiGeofence.js +++ b/src/routes/apiGeofence.js @@ -1,4 +1,5 @@ const geofenceTileGenerator = require('../lib/geofenceTileGenerator') +const { getKojiFences } = require('../util/koji') module.exports = async (fastify, options, next) => { fastify.get('/api/geofence/:area/map', options, async (req) => { @@ -20,7 +21,7 @@ module.exports = async (fastify, options, next) => { } try { - const url = await geofenceTileGenerator.generateGeofenceTile(fastify.geofence, fastify.query.tileserverPregen, req.params.area) + const url = await geofenceTileGenerator.generateGeofenceTile(fastify.geofence.geofence, fastify.query.tileserverPregen, req.params.area) return { status: 'ok', url, @@ -140,7 +141,7 @@ module.exports = async (fastify, options, next) => { } const areas = {} - for (const fence of fastify.geofence) { + for (const fence of fastify.geofence.geofence) { areas[fence.name] = require('crypto').createHash('md5').update(JSON.stringify(fence.path)).digest('hex') } @@ -173,7 +174,7 @@ module.exports = async (fastify, options, next) => { return { status: 'ok', - geofence: fastify.geofence, + geofence: fastify.geofence.geofence, } }) @@ -202,7 +203,7 @@ module.exports = async (fastify, options, next) => { type: 'FeatureCollection', features: [], } - const inGeoJSON = fastify.geofence + const inGeoJSON = fastify.geofence.geofence for (let i = 0; i < inGeoJSON.length; i++) { const inGeofence = inGeoJSON[i] @@ -225,10 +226,10 @@ module.exports = async (fastify, options, next) => { const outPath = [] if (inGeofence.multipath) { for (let j = 0; j < inGeofence.multipath.length; j++) { - const path = inGeofence.multipath[j] + const geofencePath = inGeofence.multipath[j] const outSubPath = [] - for (let k = 0; k < path.length; k++) { - const coord = path[k] + for (let k = 0; k < geofencePath.length; k++) { + const coord = geofencePath[k] outSubPath.push([coord[1], coord[0]]) } if (outSubPath.at(-1)[0] !== outSubPath[0][0] || outSubPath.at(-1)[1] !== outSubPath[0][1]) { @@ -255,5 +256,31 @@ module.exports = async (fastify, options, next) => { } }) + fastify.get('/api/geofence/reload', options, async (req) => { + fastify.logger.info(`API: ${req.ip} ${req.routeOptions.method} ${req.routeOptions.url}`) + + if (fastify.config.server.ipWhitelist.length && !fastify.config.server.ipWhitelist.includes(req.ip)) { + return { + webserver: 'unhappy', + reason: `ip ${req.ip} not in whitelist`, + } + } + if (fastify.config.server.ipBlacklist.length && fastify.config.server.ipBlacklist.includes(req.ip)) { + return { + webserver: 'unhappy', + reason: `ip ${req.ip} in blacklist`, + } + } + + const secret = req.headers['x-poracle-secret'] + if (!secret || !fastify.config.server.apiSecret || secret !== fastify.config.server.apiSecret) { + return { status: 'authError', reason: 'incorrect or missing api secret' } + } + + await getKojiFences() + + return { status: 'ok' } + }) + next() } diff --git a/src/routes/apiHumans.js b/src/routes/apiHumans.js index e9d01c161..cb5a07aa7 100644 --- a/src/routes/apiHumans.js +++ b/src/routes/apiHumans.js @@ -22,7 +22,7 @@ module.exports = async (fastify, options, next) => { } } - let allowedAreas = fastify.geofence.map((x) => x.name.toLowerCase()) + let allowedAreas = fastify.geofence.geofence.map((x) => x.name.toLowerCase()) if (fastify.config.areaSecurity.enabled && !fastify.config.discord.admins.includes(req.params.id) && !fastify.config.telegram.admins.includes(req.params.id)) { allowedAreas = communityLogic.filterAreas( @@ -35,7 +35,7 @@ module.exports = async (fastify, options, next) => { return { status: 'ok', - areas: fastify.geofence.filter((x) => allowedAreas.includes(x.name.toLowerCase())).map((x) => ({ + areas: fastify.geofence.geofence.filter((x) => allowedAreas.includes(x.name.toLowerCase())).map((x) => ({ name: x.name, group: x.group || '', description: x.description || '', @@ -421,7 +421,7 @@ module.exports = async (fastify, options, next) => { const currentProfileNo = human.current_profile_no const adminTarget = !fastify.config.discord.admins.includes(targetId) || !fastify.config.telegram.admins.includes(targetId) - let allowedAreas = fastify.geofence + let allowedAreas = fastify.geofence.geofence if (!adminTarget) allowedAreas = allowedAreas.filter((area) => (area.userSelectable === undefined || area.userSelectable)) allowedAreas = allowedAreas.map((x) => x.name.toLowerCase()) if (fastify.config.areaSecurity.enabled && !adminTarget) { diff --git a/src/routes/apiTrackingRaid.js b/src/routes/apiTrackingRaid.js index cf5dc18bf..597fb0171 100644 --- a/src/routes/apiTrackingRaid.js +++ b/src/routes/apiTrackingRaid.js @@ -102,7 +102,7 @@ module.exports = async (fastify, options, next) => { team: row.team >= 0 && row.team <= 4 ? row.team : 4, // carefully chosen to get nulls/undefined to 4 but allow 0 clean: +defaultTo(+row.clean, 0), level: +level, - form: 0, + form: +defaultTo(row.form, 0), move: +defaultTo(row.move, 9000), evolution: +defaultTo(row.evolution, 9000), gym_id: row.gym_id ? row.gym_id : null, diff --git a/src/util/koji.js b/src/util/koji.js index b084a0264..2d8ddf724 100644 --- a/src/util/koji.js +++ b/src/util/koji.js @@ -1,32 +1,11 @@ const fs = require('fs') const { resolve } = require('path') const fetch = require('node-fetch') -const stripJsonComments = require('strip-json-comments') +const config = require('config') const { log } = require('../lib/logger') -const parseConfigSafe = () => { - let config = {} - try { - if (fs.existsSync(resolve(__dirname, '../../config/local.json'))) { - const string = stripJsonComments( - fs - .readFileSync(resolve(__dirname, '../../config/local.json')) - .toString(), - ) - config = JSON.parse(string) - } else { - log.warn('[KŌJI] Local.json was not found, skipping') - } - return config - } catch (e) { - log.error('[KŌJI] Could not parse local config', e) - return config - } -} - const getKojiFences = async () => { - const config = parseConfigSafe() try { if (config?.geofence?.kojiOptions?.bearerToken) { const fences = Array.isArray(config.geofence?.path) diff --git a/tileservercache_templates/poracle-weather.json b/tileservercache_templates/poracle-weather.json index 20d3582bc..33f9849d0 100644 --- a/tileservercache_templates/poracle-weather.json +++ b/tileservercache_templates/poracle-weather.json @@ -15,7 +15,7 @@ "latitude": #(pok.latitude), "longitude": #(pok.longitude), "width": 20, - "height": 20, + "height": 20 }, #endfor #endif