@@ -14,6 +14,10 @@ const config = require('@rm/config')
1414const { getAreaSql } = require ( '../utils/getAreaSql' )
1515const { filterRTree } = require ( '../utils/filterRTree' )
1616const { fetchJson } = require ( '../utils/fetchJson' )
17+ const {
18+ applyManualIdFilter,
19+ normalizeManualId,
20+ } = require ( '../utils/manualFilter' )
1721const {
1822 IV_CALC ,
1923 LEVEL_CALC ,
@@ -148,6 +152,10 @@ class Pokemon extends Model {
148152
149153 const query = this . query ( )
150154
155+ const manualIdFilter = normalizeManualId ( args . filters . onlyManualId )
156+
157+ let manualId = manualIdFilter
158+
151159 const pokemonIds = [ ]
152160 const pokemonForms = [ ]
153161 Object . values ( filterMap ) . forEach ( ( filter ) => {
@@ -169,75 +177,81 @@ class Pokemon extends Model {
169177 } else {
170178 query . select ( [ '*' , hasSize && ! hasHeight ? 'size AS height' : 'size' ] )
171179 }
172- query
173- . where (
174- isMad ? 'disappear_time' : 'expire_timestamp' ,
175- '>=' ,
176- isMad ? this . knex ( ) . fn . now ( ) : ts ,
177- )
178- . andWhereBetween ( isMad ? 'pokemon.latitude' : 'lat' , [
179- args . minLat ,
180- args . maxLat ,
181- ] )
182- . andWhereBetween ( isMad ? 'pokemon.longitude' : 'lon' , [
183- args . minLon ,
184- args . maxLon ,
185- ] )
186- . andWhere ( ( ivOr ) => {
187- if ( ivs || pvp ) {
188- if ( globalFilter . filterKeys . size ) {
189- ivOr . andWhere ( ( pkmn ) => {
190- const keys = globalFilter . keyArray
191- for ( let i = 0 ; i < keys . length ; i += 1 ) {
192- const key = keys [ i ]
193- switch ( key ) {
194- case 'xxs' :
195- case 'xxl' :
196- if ( hasSize ) {
197- pkmn . orWhere ( 'pokemon.size' , key === 'xxl' ? 5 : 1 )
198- }
199- break
200- case 'gender' :
201- pkmn . andWhere ( 'pokemon.gender' , onlyIvOr [ key ] )
202- break
203- case 'cp' :
204- case 'level' :
205- case 'atk_iv' :
206- case 'def_iv' :
207- case 'sta_iv' :
208- case 'iv' :
209- if ( perms . iv ) {
210- pkmn . andWhereBetween (
211- isMad ? MAD_KEY_MAP [ key ] : key ,
212- onlyIvOr [ key ] ,
213- )
214- }
215- break
216- default :
217- if (
218- perms . pvp &&
219- BASE_KEYS . every ( ( x ) => ! globalFilter . filterKeys . has ( x ) )
220- ) {
221- // doesn't return everything if only pvp stats for individual pokemon
222- pkmn . whereNull ( 'pokemon_id' )
223- }
224- break
225- }
180+ query . where (
181+ isMad ? 'disappear_time' : 'expire_timestamp' ,
182+ '>=' ,
183+ isMad ? this . knex ( ) . fn . now ( ) : ts ,
184+ )
185+ manualId = applyManualIdFilter ( query , {
186+ manualId : manualIdFilter ,
187+ latColumn : isMad ? 'pokemon.latitude' : 'lat' ,
188+ lonColumn : isMad ? 'pokemon.longitude' : 'lon' ,
189+ idColumn : isMad ? 'pokemon.encounter_id' : 'id' ,
190+ bounds : {
191+ minLat : args . minLat ,
192+ maxLat : args . maxLat ,
193+ minLon : args . minLon ,
194+ maxLon : args . maxLon ,
195+ } ,
196+ } )
197+ query . andWhere ( ( ivOr ) => {
198+ if ( ivs || pvp ) {
199+ if ( globalFilter . filterKeys . size ) {
200+ ivOr . andWhere ( ( pkmn ) => {
201+ const keys = globalFilter . keyArray
202+ for ( let i = 0 ; i < keys . length ; i += 1 ) {
203+ const key = keys [ i ]
204+ switch ( key ) {
205+ case 'xxs' :
206+ case 'xxl' :
207+ if ( hasSize ) {
208+ pkmn . orWhere ( 'pokemon.size' , key === 'xxl' ? 5 : 1 )
209+ }
210+ break
211+ case 'gender' :
212+ pkmn . andWhere ( 'pokemon.gender' , onlyIvOr [ key ] )
213+ break
214+ case 'cp' :
215+ case 'level' :
216+ case 'atk_iv' :
217+ case 'def_iv' :
218+ case 'sta_iv' :
219+ case 'iv' :
220+ if ( perms . iv ) {
221+ pkmn . andWhereBetween (
222+ isMad ? MAD_KEY_MAP [ key ] : key ,
223+ onlyIvOr [ key ] ,
224+ )
225+ }
226+ break
227+ default :
228+ if (
229+ perms . pvp &&
230+ BASE_KEYS . every ( ( x ) => ! globalFilter . filterKeys . has ( x ) )
231+ ) {
232+ // doesn't return everything if only pvp stats for individual pokemon
233+ pkmn . whereNull ( 'pokemon_id' )
234+ }
235+ break
226236 }
227- } )
228- } else {
229- ivOr . whereNull ( 'pokemon_id' )
230- }
231- ivOr . orWhereIn ( 'pokemon_id' , pokemonIds )
232- ivOr . orWhereIn ( 'pokemon.form' , pokemonForms )
233- }
234- if ( onlyZeroIv && ivs ) {
235- ivOr . orWhere ( isMad ? raw ( IV_CALC ) : 'iv' , 0 )
236- }
237- if ( onlyHundoIv && ivs ) {
238- ivOr . orWhere ( isMad ? raw ( IV_CALC ) : 'iv' , 100 )
237+ }
238+ } )
239+ } else {
240+ ivOr . whereNull ( 'pokemon_id' )
239241 }
240- } )
242+ ivOr . orWhereIn ( 'pokemon_id' , pokemonIds )
243+ ivOr . orWhereIn ( 'pokemon.form' , pokemonForms )
244+ }
245+ if ( onlyZeroIv && ivs ) {
246+ ivOr . orWhere ( isMad ? raw ( IV_CALC ) : 'iv' , 0 )
247+ }
248+ if ( onlyHundoIv && ivs ) {
249+ ivOr . orWhere ( isMad ? raw ( IV_CALC ) : 'iv' , 100 )
250+ }
251+ if ( manualId !== null ) {
252+ ivOr . orWhereIn ( isMad ? 'pokemon.encounter_id' : 'id' , [ manualId ] )
253+ }
254+ } )
241255 if ( ! getAreaSql ( query , areaRestrictions , onlyAreas , isMad , 'pokemon' ) ) {
242256 return [ ]
243257 }
@@ -272,7 +286,7 @@ class Pokemon extends Model {
272286 filters . push ( { iv : { min : 100 , max : 100 } , pokemon : globalPokes } )
273287 }
274288 /** @type {import("@rm/types").Pokemon[] } */
275- const results = await this . evalQuery (
289+ let results = await this . evalQuery (
276290 mem ? `${ mem } /api/pokemon/v2/scan` : null ,
277291 mem
278292 ? JSON . stringify ( {
@@ -293,6 +307,30 @@ class Pokemon extends Model {
293307 httpAuth ,
294308 )
295309
310+ if ( mem && manualId !== null ) {
311+ const loadedIds = Array . isArray ( results )
312+ ? new Set ( results . map ( ( pkmn ) => `${ pkmn . id } ` ) )
313+ : new Set ( )
314+ if ( ! loadedIds . has ( `${ manualId } ` ) ) {
315+ const manualResult = await this . evalQuery (
316+ `${ mem } /api/pokemon/id/${ manualId } ` ,
317+ null ,
318+ 'GET' ,
319+ secret ,
320+ httpAuth ,
321+ ) . catch ( ( ) => null )
322+ if ( manualResult ) {
323+ results = Array . isArray ( results )
324+ ? [ ...results , manualResult ]
325+ : [ manualResult ]
326+ }
327+ }
328+ }
329+
330+ if ( ! Array . isArray ( results ) ) {
331+ results = [ ]
332+ }
333+
296334 const finalResults = [ ]
297335 const pvpResults = [ ]
298336 const listOfIds = [ ]
@@ -326,20 +364,23 @@ class Pokemon extends Model {
326364 if ( isMad ) {
327365 Pokemon . getMadSql ( pvpQuery )
328366 }
329- pvpQuery
330- . where (
331- isMad ? 'disappear_time' : 'expire_timestamp' ,
332- '>=' ,
333- isMad ? this . knex ( ) . fn . now ( ) : ts ,
334- )
335- . andWhereBetween ( isMad ? 'pokemon.latitude' : 'lat' , [
336- args . minLat ,
337- args . maxLat ,
338- ] )
339- . andWhereBetween ( isMad ? 'pokemon.longitude' : 'lon' , [
340- args . minLon ,
341- args . maxLon ,
342- ] )
367+ pvpQuery . where (
368+ isMad ? 'disappear_time' : 'expire_timestamp' ,
369+ '>=' ,
370+ isMad ? this . knex ( ) . fn . now ( ) : ts ,
371+ )
372+ applyManualIdFilter ( pvpQuery , {
373+ manualId,
374+ latColumn : isMad ? 'pokemon.latitude' : 'lat' ,
375+ lonColumn : isMad ? 'pokemon.longitude' : 'lon' ,
376+ idColumn : isMad ? 'pokemon.encounter_id' : 'id' ,
377+ bounds : {
378+ minLat : args . minLat ,
379+ maxLat : args . maxLat ,
380+ minLon : args . minLon ,
381+ maxLon : args . maxLon ,
382+ } ,
383+ } )
343384 if ( isMad && listOfIds . length ) {
344385 pvpQuery . whereRaw (
345386 `pokemon.encounter_id NOT IN ( ${ listOfIds . join ( ',' ) } )` ,
@@ -643,6 +684,7 @@ class Pokemon extends Model {
643684 const { isMad, hasSize, hasHeight, mem, secret, httpAuth } = ctx
644685 const ts = Math . floor ( Date . now ( ) / 1000 )
645686 const { filterMap, globalFilter } = this . getFilters ( perms , args , ctx )
687+ const manualIdFilter = normalizeManualId ( args . filters . onlyManualId )
646688 const queryLimits = config . getSafe ( 'api.queryLimits' )
647689
648690 if ( ! perms . iv && ! perms . pvp ) {
@@ -682,14 +724,25 @@ class Pokemon extends Model {
682724 ) {
683725 return [ ]
684726 }
685-
727+ const manualId = applyManualIdFilter ( query , {
728+ manualId : manualIdFilter ,
729+ latColumn : isMad ? 'pokemon.latitude' : 'lat' ,
730+ lonColumn : isMad ? 'pokemon.longitude' : 'lon' ,
731+ idColumn : isMad ? 'pokemon.encounter_id' : 'id' ,
732+ bounds : {
733+ minLat : args . minLat ,
734+ maxLat : args . maxLat ,
735+ minLon : args . minLon ,
736+ maxLon : args . maxLon ,
737+ } ,
738+ } )
686739 const filters = mem
687740 ? Object . values ( filterMap ) . flatMap ( ( filter ) => filter . buildApiFilter ( ) )
688741 : [ ]
689742 if ( ( perms . iv || perms . pvp ) && mem )
690743 filters . push ( ...globalFilter . buildApiFilter ( ) )
691744
692- const results = await this . evalQuery (
745+ let results = await this . evalQuery (
693746 mem ? `${ mem } /api/pokemon/v2/scan` : null ,
694747 mem
695748 ? JSON . stringify ( {
@@ -709,6 +762,22 @@ class Pokemon extends Model {
709762 secret ,
710763 httpAuth ,
711764 )
765+
766+ if ( mem && manualId !== null ) {
767+ const loaded = new Set ( results . map ( ( pkmn ) => `${ pkmn . id } ` ) )
768+ if ( ! loaded . has ( `${ manualId } ` ) ) {
769+ const manualResult = await this . evalQuery (
770+ `${ mem } /api/pokemon/id/${ manualId } ` ,
771+ null ,
772+ 'GET' ,
773+ secret ,
774+ httpAuth ,
775+ ) . catch ( ( ) => null )
776+ if ( manualResult ) {
777+ results = [ ...results , manualResult ]
778+ }
779+ }
780+ }
712781 const filtered = results . filter (
713782 ( item ) =>
714783 ! mem ||
0 commit comments