Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# [1.54.0](https://github.com/WatWowMap/ReactMap/compare/v1.53.2...v1.54.0) (2026-10-20)


### Features

* **golbat:** discover instance capabilities from /api/status ([#1258](https://github.com/WatWowMap/ReactMap/issues/1258)) ([c83f93f](https://github.com/WatWowMap/ReactMap/commit/c83f93f6e61eeb8f403bf44d160510f1073807d3))

## [1.53.2](https://github.com/WatWowMap/ReactMap/compare/v1.53.1...v1.53.2) (2026-10-10)


Expand Down
41 changes: 40 additions & 1 deletion config/custom-environment-variables.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
"__name": "API_SHOW_STRATEGIES_IN_CONFIG_API",
"__format": "boolean"
},
"hideEncounterIds": {
"__name": "API_HIDE_ENCOUNTER_IDS",
"__format": "boolean"
},
"maxSessions": {
"__name": "API_MAX_SESSIONS",
"__format": "number"
Expand Down Expand Up @@ -519,6 +523,10 @@
"__name": "MAP_MISC_ENABLE_POKEMON_POPUP_COORDS_SELECTOR",
"__format": "boolean"
},
"enablePersonalShiny": {
"__name": "MAP_MISC_ENABLE_PERSONAL_SHINY",
"__format": "boolean"
},
"enableGymPopupCoordsSelector": {
"__name": "MAP_MISC_ENABLE_GYM_POPUP_COORDS_SELECTOR",
"__format": "boolean"
Expand Down Expand Up @@ -994,6 +1002,19 @@
"__name": "CLIENT_SIDE_OPTIONS_POKEMON_SPACIAL_REND_RANGE",
"__format": "boolean"
},
"personalShinyIds": "CLIENT_SIDE_OPTIONS_POKEMON_PERSONAL_SHINY_IDS",
"onlyPersonalShiny": {
"__name": "CLIENT_SIDE_OPTIONS_POKEMON_ONLY_PERSONAL_SHINY",
"__format": "boolean"
},
"onlyShundo": {
"__name": "CLIENT_SIDE_OPTIONS_POKEMON_ONLY_SHUNDO",
"__format": "boolean"
},
"shinySparkles": {
"__name": "CLIENT_SIDE_OPTIONS_POKEMON_SHINY_SPARKLES",
"__format": "boolean"
},
"glow": {
"__name": "CLIENT_SIDE_OPTIONS_POKEMON_GLOW",
"__format": "json"
Expand Down Expand Up @@ -1301,6 +1322,10 @@
"__name": "DEFAULT_FILTERS_POKEMON_ALL_POKEMON",
"__format": "boolean"
},
"shinyIv": {
"__name": "DEFAULT_FILTERS_POKEMON_SHINY_IV",
"__format": "json"
},
"globalValues": {
"iv": {
"__name": "DEFAULT_FILTERS_POKEMON_GLOBAL_VALUES_IV",
Expand Down Expand Up @@ -2100,6 +2125,20 @@
"__name": "AUTHENTICATION_PERMS_DYNAMAX_ROLES",
"__format": "json"
}
},
"personalShiny": {
"enabled": {
"__name": "AUTHENTICATION_PERMS_PERSONAL_SHINY_ENABLED",
"__format": "boolean"
},
"trialPeriodEligible": {
"__name": "AUTHENTICATION_PERMS_PERSONAL_SHINY_TRIAL_PERIOD_ELIGIBLE",
"__format": "boolean"
},
"roles": {
"__name": "AUTHENTICATION_PERMS_PERSONAL_SHINY_ROLES",
"__format": "json"
}
}
}
},
Expand Down Expand Up @@ -2463,4 +2502,4 @@
}
}
}
}
}
12 changes: 12 additions & 0 deletions config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"enableHelmet": false,
"showSchemasInConfigApi": false,
"showStrategiesInConfigApi": false,
"hideEncounterIds": true,
"maxSessions": 5,
"sessionCheckIntervalMs": 900000,
"cookieAgeDays": 7,
Expand Down Expand Up @@ -262,6 +263,7 @@
"clientTimeoutMinutes": 30,
"distanceUnit": "kilometers",
"enablePokemonPopupCoordsSelector": false,
"enablePersonalShiny": false,
"enableGymPopupCoordsSelector": false,
"enablePokestopPopupCoordsSelector": false,
"enablePortalPopupCoordsSelector": false,
Expand Down Expand Up @@ -425,6 +427,10 @@
"showSizeIndicator": false,
"enablePokemonPopupCoords": false,
"spacialRendRange": false,
"personalShinyIds": "",
"onlyPersonalShiny": false,
"onlyShundo": false,
"shinySparkles": true,
"glow": [
{
"name": "Hundo",
Expand Down Expand Up @@ -564,6 +570,7 @@
"onlyShowAvailable": true,
"legacyFilter": false,
"allPokemon": false,
"shinyIv": [0, 100],
"globalValues": {
"iv": [80, 100],
"level": [1, 55],
Expand Down Expand Up @@ -923,6 +930,11 @@
"enabled": true,
"trialPeriodEligible": false,
"roles": []
},
"personalShiny": {
"enabled": true,
"trialPeriodEligible": false,
"roles": []
}
}
},
Expand Down
5 changes: 5 additions & 0 deletions config/local.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,11 @@
"enabled": true,
"trialPeriodEligible": false,
"roles": []
},
"personalShiny": {
"enabled": true,
"trialPeriodEligible": false,
"roles": []
}
}
},
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "reactmap",
"version": "1.53.2",
"version": "1.54.0",
"private": true,
"description": "React based frontend map.",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/config/.configref
Original file line number Diff line number Diff line change
@@ -1 +1 @@
26174
26204
8 changes: 8 additions & 0 deletions packages/locales/lib/human/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"categories": "Categories",
"rank": "Rank",
"slider_iv": "IV Range",
"slider_shinyIv": "Shiny IV Range",
"slider_great": "Great League",
"slider_ultra": "Ultra League",
"slider_little": "Little Cup",
Expand Down Expand Up @@ -383,6 +384,13 @@
"without_ar": "Without AR",
"shiny_probability": "Shiny rate: <0/>",
"shiny_sample": "{{percentage}}%: {{shiny}} shiny/{{checks}} checks since {{date}}",
"personal_shiny_ids": "Player IDs for Personal Shinies",
"personal_shiny_for": "✨ Shiny for {{ids}}",
"only_personal_shiny": "Only Show Personal Shinies",
"personal_shiny": "Personal Shinies",
"personal_shiny_subtitle": "Highlights Pokémon that are shiny for your player IDs",
"only_shundo": "Only Show Shundos (Shiny 100% IV)",
"shiny_sparkles": "Animated Sparkles for Shinies 70% IV and Up",
"exclude_quest_multi": "Exclude {{reward}}",
"exclude_battle_multi": "Exclude {{battle}}",
"cluster_limit_0": "{{variable_0}} limit ({{variable_1}}) has been hit",
Expand Down
1 change: 1 addition & 0 deletions packages/logger/lib/tags.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const TAGS = /** @type {const} */ ({
geocoder: chalk.hex('#ff5722')('[GEOCODER]'),
fetch: chalk.hex('#880e4f')('[FETCH]'),
scanner: chalk.hex('#b39ddb')('[SCANNER]'),
golbat: chalk.hex('#ffb300')('[GOLBAT]'),
build: chalk.hex('#ef6c00')('[BUILD]'),
ReactMap: chalk.hex('#ff3d00')('[ReactMap]'),
trial: chalk.hex('#fff320')('[TRIAL]'),
Expand Down
1 change: 1 addition & 0 deletions packages/types/lib/scanner.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ export interface Pokemon {
pvp: CleanPvp
distance?: number
shiny?: boolean
shiny_for?: string[]
}

export type FullPokemon = FullModel<Pokemon, PokemonModel.Pokemon>
Expand Down
37 changes: 36 additions & 1 deletion packages/types/lib/server.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,13 @@ export interface AvailablePokestopShowcase {
}

export interface AvailablePokestops {
showcase_focus_filter: boolean
/**
* @deprecated Golbat advertises showcase focus support via
* GET /api/status `filters.showcase_focus` (see GolbatStatus). This
* per-response flag is only consulted for a Golbat whose status route
* reports no filters block, and will be dropped once such builds are gone.
*/
showcase_focus_filter?: boolean
quests: AvailablePokestopQuest[]
invasions: AvailablePokestopInvasion[]
lures: AvailablePokestopLure[]
Expand All @@ -125,6 +131,35 @@ export interface Available {
tappables: ModelReturn<typeof Tappable, 'getAvailable'>
}

/**
* Parsed GET /api/status from one Golbat instance. Every key a build defines
* is true; the signal is the key's presence. `filters` is null when the build
* predates the block (it then advertises no optional DNF filter fields), so
* consumers can fall back to older per-response flags for that build only.
*/
export interface GolbatStatus {
features: Record<string, boolean>
limits: Record<string, number>
filters: Record<string, boolean> | null
}

/** Registry entry kept by GolbatCapabilities, keyed by the endpoint base url. */
export interface GolbatInstance {
mem: string
/** Every distinct credential set configured for this url, in config order. */
credentials: {
secret: string
httpAuth: { username: string; password: string } | null
}[]
/** Index into `credentials` of the set that last got through; tried first. */
credentialIndex: number
/** Last good status; null until the instance has answered once. */
status: GolbatStatus | null
/** Epoch ms of the last refresh start; drives the recheck debounce. */
lastFetchAt: number
inflight: Promise<void> | null
}

export interface ApiEndpoint {
type: string
endpoint: string
Expand Down
6 changes: 6 additions & 0 deletions server/src/filters/builder/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,12 @@ function buildDefaultFilters(perms) {
gender: defaultFilters.pokemon.globalValues.gender,
zeroIv: perms.iv ? false : undefined,
hundoIv: perms.iv ? true : undefined,
shinyIv:
perms.iv &&
perms.personalShiny &&
config.getSafe('map.misc.enablePersonalShiny')
? defaultFilters.pokemon.shinyIv
: undefined,
filter: pokemon.full,
}
: undefined,
Expand Down
6 changes: 5 additions & 1 deletion server/src/filters/pokemon/Backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const {
dnfifyIvFilter,
} = require('./functions')
const { filterRTree } = require('../../utils/filterRTree')
const { encodeEncounterId } = require('../../services/encounterToken')
const { PokemonFilter } = require('./Frontend')

class PkmnBackend {
Expand Down Expand Up @@ -404,7 +405,7 @@ class PkmnBackend {
*/
build(pokemon) {
const result = {
id: pokemon.id,
id: encodeEncounterId(pokemon.id),
encounter_id: pokemon.encounter_id,
spawnpoint_id: pokemon.spawnpoint_id,
lat: pokemon.lat,
Expand All @@ -431,6 +432,9 @@ class PkmnBackend {
}

if (!this.perms.pokemon) return result
if (Array.isArray(pokemon.shiny_for) && pokemon.shiny_for.length) {
result.shiny_for = pokemon.shiny_for
}
if (this.perms.iv) {
assign(result, pokemon, [
'atk_iv',
Expand Down
35 changes: 29 additions & 6 deletions server/src/graphql/resolvers.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ const { validateSelectedWebhook } = require('../utils/validateSelectedWebhook')
const { PoracleAPI } = require('../services/Poracle')
const { geocoder } = require('../services/geocoder')
const { scannerApi } = require('../services/scannerApi')
const {
encodeEncounterId,
decodeEncounterId,
} = require('../services/encounterToken')
const { getPolyVector } = require('../utils/getPolyVector')
const { getPlacementCells } = require('../utils/getPlacementCells')
const { getTypeCells } = require('../utils/getTypeCells')
Expand Down Expand Up @@ -273,16 +277,28 @@ const resolvers = {
},
pokemon: (_, args, { perms, Db }) => {
if (perms?.pokemon) {
const decodedArgs = {
...args,
filters: {
...args.filters,
onlyManualId: decodeEncounterId(args.filters.onlyManualId),
},
}
if (args.filters.onlyLegacy) {
return Db.query('Pokemon', 'getLegacy', perms, args)
return Db.query('Pokemon', 'getLegacy', perms, decodedArgs)
}
return Db.query('Pokemon', 'getAll', perms, args)
return Db.query('Pokemon', 'getAll', perms, decodedArgs)
}
return []
},
pokemonSingle: (_, args, { perms, Db }) => {
pokemonSingle: async (_, args, { perms, Db }) => {
if (perms?.pokemon) {
return Db.getOne('Pokemon', args.id)
const id = decodeEncounterId(args.id, { allowRaw: true })
if (id === null) return {}
const result = await Db.getOne('Pokemon', id)
return 'lat' in result
? { ...result, id: encodeEncounterId(id) }
: result
}
return {}
},
Expand Down Expand Up @@ -551,8 +567,15 @@ const resolvers = {
},
tappableById: async (_, { id }, { perms, Db }) => {
if (perms?.tappables) {
const results = await Db.query('Tappable', 'getById', perms, id)
return Array.isArray(results) ? results[0] || null : results
const tappableId = decodeEncounterId(id)
if (tappableId === null) return null
const results = await Db.query('Tappable', 'getById', perms, tappableId)
const result = Array.isArray(results) ? results[0] || null : results
if (result && config.getSafe('api.hideEncounterIds')) {
// a pokemon's tappable shares its encounter ID, so echo the token
return { ...result, id }
}
return result
}
return null
},
Expand Down
1 change: 1 addition & 0 deletions server/src/graphql/typeDefs/scanner.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ type Pokemon {
sta_iv: Int
weather: Int
background: Int
shiny_for: [String]
capture_1: Float
capture_2: Float
capture_3: Float
Expand Down
Loading
Loading