@nekosuneprojects/gaming-infos 2.2.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/sync-api-cache.yml +3 -2
- package/CACHE_SYNC.md +84 -54
- package/CHANGELOG.md +34 -3
- package/README.md +210 -371
- package/data/.cache-manifest.json +112 -0
- package/data/arknightsendfield/characters/akekuri.json +75 -0
- package/data/arknightsendfield/characters/alesh.json +75 -0
- package/data/arknightsendfield/characters/antal.json +75 -0
- package/data/arknightsendfield/characters/arcane.json +75 -0
- package/data/arknightsendfield/characters/arclight.json +75 -0
- package/data/arknightsendfield/characters/ardelia.json +75 -0
- package/data/arknightsendfield/characters/avywenna.json +76 -0
- package/data/arknightsendfield/characters/camille.json +77 -0
- package/data/arknightsendfield/characters/catcher.json +76 -0
- package/data/arknightsendfield/characters/chenqianyu.json +76 -0
- package/data/arknightsendfield/characters/dapan.json +76 -0
- package/data/arknightsendfield/characters/ember.json +76 -0
- package/data/arknightsendfield/characters/endministrator.json +63 -0
- package/data/arknightsendfield/characters/estella.json +76 -0
- package/data/arknightsendfield/characters/fluorite.json +75 -0
- package/data/arknightsendfield/characters/gilberta.json +76 -0
- package/data/arknightsendfield/characters/laevatain.json +76 -0
- package/data/arknightsendfield/characters/lastrite.json +76 -0
- package/data/arknightsendfield/characters/lifeng.json +75 -0
- package/data/arknightsendfield/characters/liino.json +76 -0
- package/data/arknightsendfield/characters/mifu.json +75 -0
- package/data/arknightsendfield/characters/perlica.json +76 -0
- package/data/arknightsendfield/characters/pogranichnik.json +75 -0
- package/data/arknightsendfield/characters/purrchena.json +50 -0
- package/data/arknightsendfield/characters/rossi.json +77 -0
- package/data/arknightsendfield/characters/snowshine.json +75 -0
- package/data/arknightsendfield/characters/tangtang.json +76 -0
- package/data/arknightsendfield/characters/typhoeus.json +57 -0
- package/data/arknightsendfield/characters/wulfgard.json +77 -0
- package/data/arknightsendfield/characters/xaihi.json +76 -0
- package/data/arknightsendfield/characters/yvonne.json +76 -0
- package/data/arknightsendfield/characters/zhuangfangyi.json +76 -0
- package/data/fortnite/characters/agentjones.json +43 -0
- package/data/fortnite/characters/agentpeely.json +43 -0
- package/data/fortnite/characters/andyfangerson.json +42 -0
- package/data/fortnite/characters/antonia.json +43 -0
- package/data/fortnite/characters/anwar.json +44 -0
- package/data/fortnite/characters/aphrodite.json +43 -0
- package/data/fortnite/characters/apollo.json +36 -0
- package/data/fortnite/characters/arcticassassin.json +43 -0
- package/data/fortnite/characters/artemis.json +36 -0
- package/data/fortnite/characters/battlewoodboulevard.json +43 -0
- package/data/fortnite/characters/beastmode.json +45 -0
- package/data/fortnite/characters/bogstick.json +43 -0
- package/data/fortnite/characters/boxy.json +44 -0
- package/data/fortnite/characters/bravoleader.json +44 -0
- package/data/fortnite/characters/briteraider.json +43 -0
- package/data/fortnite/characters/brutalboxcars.json +46 -0
- package/data/fortnite/characters/bryne.json +44 -0
- package/data/fortnite/characters/buildersbarracks.json +38 -0
- package/data/fortnite/characters/bullshark.json +43 -0
- package/data/fortnite/characters/bumpybay.json +44 -0
- package/data/fortnite/characters/burd.json +44 -0
- package/data/fortnite/characters/burdemart.json +39 -0
- package/data/fortnite/characters/burdtogo.json +42 -0
- package/data/fortnite/characters/busansanctuary.json +39 -0
- package/data/fortnite/characters/calamaricanyon.json +41 -0
- package/data/fortnite/characters/canyoncrossing.json +45 -0
- package/data/fortnite/characters/captainjones.json +40 -0
- package/data/fortnite/characters/centurion.json +44 -0
- package/data/fortnite/characters/cerberus.json +43 -0
- package/data/fortnite/characters/charon.json +43 -0
- package/data/fortnite/characters/chiara.json +43 -0
- package/data/fortnite/characters/clara.json +43 -0
- package/data/fortnite/characters/classifiedcanyon.json +43 -0
- package/data/fortnite/characters/clawsylodge.json +41 -0
- package/data/fortnite/characters/cletuscornhole.json +43 -0
- package/data/fortnite/characters/contractgiller.json +44 -0
- package/data/fortnite/characters/corruptioncorners.json +42 -0
- package/data/fortnite/characters/countessdaraku.json +45 -0
- package/data/fortnite/characters/crackshotscabin.json +40 -0
- package/data/fortnite/characters/creepycamps.json +36 -0
- package/data/fortnite/characters/crimecity.json +47 -0
- package/data/fortnite/characters/dara.json +42 -0
- package/data/fortnite/characters/darkruby.json +43 -0
- package/data/fortnite/characters/demonsdojo.json +46 -0
- package/data/fortnite/characters/diamonddiva.json +43 -0
- package/data/fortnite/characters/donutdistrict.json +43 -0
- package/data/fortnite/characters/drakonsteelrider.json +43 -0
- package/data/fortnite/characters/dummy.json +45 -0
- package/data/fortnite/characters/edwardscissorhands.json +39 -0
- package/data/fortnite/characters/etheria.json +43 -0
- package/data/fortnite/characters/evergreenterrace.json +41 -0
- package/data/fortnite/characters/fishmarket.json +39 -0
- package/data/fortnite/characters/fishstick.json +45 -0
- package/data/fortnite/characters/fishthicc.json +43 -0
- package/data/fortnite/characters/fortressofsolitude.json +37 -0
- package/data/fortnite/characters/foxyfloodgate.json +43 -0
- package/data/fortnite/characters/freakyfields.json +38 -0
- package/data/fortnite/characters/goldengrove.json +41 -0
- package/data/fortnite/characters/graveheart.json +43 -0
- package/data/fortnite/characters/grievouscampsite.json +38 -0
- package/data/fortnite/characters/guardianamara.json +44 -0
- package/data/fortnite/characters/gwenpool.json +41 -0
- package/data/fortnite/characters/hanamuracourtyard.json +27 -0
- package/data/fortnite/characters/heartbreakranger.json +42 -0
- package/data/fortnite/characters/heatwaveharbor.json +41 -0
- package/data/fortnite/characters/heroichope.json +44 -0
- package/data/fortnite/characters/hope.json +43 -0
- package/data/fortnite/characters/hopefulheights.json +46 -0
- package/data/fortnite/characters/humblehills.json +41 -0
- package/data/fortnite/characters/innovatorslone.json +43 -0
- package/data/fortnite/characters/insight.json +43 -0
- package/data/fortnite/characters/ironman.json +40 -0
- package/data/fortnite/characters/jittershock.json +44 -0
- package/data/fortnite/characters/joeycaliente.json +44 -0
- package/data/fortnite/characters/jonithescarlet.json +43 -0
- package/data/fortnite/characters/khabylame.json +40 -0
- package/data/fortnite/characters/khari.json +44 -0
- package/data/fortnite/characters/kitbash.json +44 -0
- package/data/fortnite/characters/kitesflight.json +41 -0
- package/data/fortnite/characters/knightmare.json +43 -0
- package/data/fortnite/characters/laguna.json +43 -0
- package/data/fortnite/characters/lakespringfield.json +35 -0
- package/data/fortnite/characters/lakespringfieldgasstation.json +39 -0
- package/data/fortnite/characters/lanallane.json +42 -0
- package/data/fortnite/characters/lattelanding.json +41 -0
- package/data/fortnite/characters/lavendra.json +44 -0
- package/data/fortnite/characters/leelah.json +45 -0
- package/data/fortnite/characters/liftylodge.json +42 -0
- package/data/fortnite/characters/lonewolflair.json +42 -0
- package/data/fortnite/characters/longshot.json +41 -0
- package/data/fortnite/characters/lostlake.json +44 -0
- package/data/fortnite/characters/loveranger.json +44 -0
- package/data/fortnite/characters/mae.json +43 -0
- package/data/fortnite/characters/magicmosses.json +42 -0
- package/data/fortnite/characters/maskedmeadows.json +48 -0
- package/data/fortnite/characters/mechateamshadow.json +44 -0
- package/data/fortnite/characters/medusa.json +43 -0
- package/data/fortnite/characters/meowscles.json +42 -0
- package/data/fortnite/characters/mephisto.json +41 -0
- package/data/fortnite/characters/metalmouth.json +44 -0
- package/data/fortnite/characters/mincemeat.json +43 -0
- package/data/fortnite/characters/munitionsexpert.json +42 -0
- package/data/fortnite/characters/myna.json +42 -0
- package/data/fortnite/characters/newsanctuary.json +43 -0
- package/data/fortnite/characters/nia.json +42 -0
- package/data/fortnite/characters/nightshiftforest.json +43 -0
- package/data/fortnite/characters/nisha.json +42 -0
- package/data/fortnite/characters/nolanchance.json +43 -0
- package/data/fortnite/characters/noorah.json +44 -0
- package/data/fortnite/characters/odyssey.json +43 -0
- package/data/fortnite/characters/openaironsen.json +40 -0
- package/data/fortnite/characters/oscar.json +42 -0
- package/data/fortnite/characters/outlawoasis.json +40 -0
- package/data/fortnite/characters/outpostenclave.json +27 -0
- package/data/fortnite/characters/oxrhq.json +37 -0
- package/data/fortnite/characters/paintedpalms.json +41 -0
- package/data/fortnite/characters/peabody.json +42 -0
- package/data/fortnite/characters/peelosopherbananocrates.json +43 -0
- package/data/fortnite/characters/peely.json +41 -0
- package/data/fortnite/characters/peelybone.json +43 -0
- package/data/fortnite/characters/persephone.json +42 -0
- package/data/fortnite/characters/phaedra.json +42 -0
- package/data/fortnite/characters/phantommeowscles.json +43 -0
- package/data/fortnite/characters/piperpace.json +43 -0
- package/data/fortnite/characters/poseidon.json +42 -0
- package/data/fortnite/characters/pumpedpower.json +40 -0
- package/data/fortnite/characters/pumpinpipes.json +39 -0
- package/data/fortnite/characters/pumpkinking.json +40 -0
- package/data/fortnite/characters/purradisemeowscles.json +43 -0
- package/data/fortnite/characters/rangersrest.json +36 -0
- package/data/fortnite/characters/rebeloutpost.json +37 -0
- package/data/fortnite/characters/renegadeshadow.json +44 -0
- package/data/fortnite/characters/resistancebase.json +27 -0
- package/data/fortnite/characters/rippedtides.json +43 -0
- package/data/fortnite/characters/rollingblossomsfarm.json +41 -0
- package/data/fortnite/characters/royalebomber.json +38 -0
- package/data/fortnite/characters/ruckus.json +42 -0
- package/data/fortnite/characters/rust.json +41 -0
- package/data/fortnite/characters/sakuracircle.json +36 -0
- package/data/fortnite/characters/sandystrip.json +44 -0
- package/data/fortnite/characters/saskia.json +45 -0
- package/data/fortnite/characters/seaportcity.json +48 -0
- package/data/fortnite/characters/shakensanctuary.json +42 -0
- package/data/fortnite/characters/shimmerdusk.json +41 -0
- package/data/fortnite/characters/shinyshafts.json +38 -0
- package/data/fortnite/characters/shogunssolitude.json +46 -0
- package/data/fortnite/characters/shuri.json +39 -0
- package/data/fortnite/characters/silashesk.json +42 -0
- package/data/fortnite/characters/slurproom.json +40 -0
- package/data/fortnite/characters/snowheart.json +44 -0
- package/data/fortnite/characters/snowstriker.json +43 -0
- package/data/fortnite/characters/solidsnake.json +39 -0
- package/data/fortnite/characters/sonor.json +46 -0
- package/data/fortnite/characters/spartanassassin.json +43 -0
- package/data/fortnite/characters/spirithuntersaeko.json +41 -0
- package/data/fortnite/characters/springfieldnationalparkstore.json +38 -0
- package/data/fortnite/characters/springfieldnuclearpowerplant.json +39 -0
- package/data/fortnite/characters/springfieldslurpworks.json +40 -0
- package/data/fortnite/characters/squibblyshores.json +42 -0
- package/data/fortnite/characters/squibblyspointlighthouse.json +37 -0
- package/data/fortnite/characters/starroakley.json +45 -0
- package/data/fortnite/characters/steelsight.json +43 -0
- package/data/fortnite/characters/sticks.json +35 -0
- package/data/fortnite/characters/sunkenshores.json +42 -0
- package/data/fortnite/characters/sunstrider.json +42 -0
- package/data/fortnite/characters/suntanspecialist.json +43 -0
- package/data/fortnite/characters/supernovaacademy.json +27 -0
- package/data/fortnite/characters/t60powerarmor.json +39 -0
- package/data/fortnite/characters/thebattlewoods.json +44 -0
- package/data/fortnite/characters/thegreatturtle.json +37 -0
- package/data/fortnite/characters/theoperator.json +42 -0
- package/data/fortnite/characters/thequackaway.json +39 -0
- package/data/fortnite/characters/thestabilizer.json +38 -0
- package/data/fortnite/characters/theunderwriter.json +36 -0
- package/data/fortnite/characters/toyboxdocks.json +40 -0
- package/data/fortnite/characters/trace.json +43 -0
- package/data/fortnite/characters/triagetrooper.json +43 -0
- package/data/fortnite/characters/triggerfish.json +44 -0
- package/data/fortnite/characters/trogscave.json +38 -0
- package/data/fortnite/characters/utopiacity.json +27 -0
- package/data/fortnite/characters/vengeancejones.json +42 -0
- package/data/fortnite/characters/victoriasaint.json +43 -0
- package/data/fortnite/characters/vineyshafts.json +35 -0
- package/data/fortnite/characters/volpez.json +41 -0
- package/data/fortnite/characters/warmachine.json +40 -0
- package/data/fortnite/characters/wastelandermagneto.json +40 -0
- package/data/fortnite/characters/wonkeeland.json +41 -0
- package/data/game-codes/.cache-manifest.json +148 -0
- package/data/game-codes/directory.json +1196 -0
- package/data/game-codes/games/arknights-endfield.json +958 -0
- package/data/game-codes/games/borderlands-1.json +414 -0
- package/data/game-codes/games/borderlands-2.json +340 -0
- package/data/game-codes/games/borderlands-3.json +1025 -0
- package/data/game-codes/games/borderlands-4.json +3128 -0
- package/data/game-codes/games/borderlands-the-pre-sequel.json +599 -0
- package/data/game-codes/games/brawl-stars.json +4766 -0
- package/data/game-codes/games/clash-of-clans.json +4766 -0
- package/data/game-codes/games/clash-royale.json +5 -0
- package/data/game-codes/games/cookie-run-kingdom.json +5366 -0
- package/data/game-codes/games/fortnite.json +897 -0
- package/data/game-codes/games/genshin-impact.json +8520 -0
- package/data/game-codes/games/honkai-impact-3rd.json +405 -0
- package/data/game-codes/games/honkai-star-rail.json +9863 -0
- package/data/game-codes/games/neverness-to-everness.json +1695 -0
- package/data/game-codes/games/roblox-doors.json +1842 -0
- package/data/game-codes/games/roblox-rivals.json +2919 -0
- package/data/game-codes/games/tower-of-fantasy.json +2839 -0
- package/data/game-codes/games/warframe.json +20824 -0
- package/data/game-codes/games/wuthering-waves.json +2477 -0
- package/data/game-codes/games/zenless-zone-zero.json +3681 -0
- package/data/game-codes/status.json +580 -0
- package/data/genshinimpact/characters/aino.json +34 -0
- package/data/genshinimpact/characters/albedo.json +10 -9
- package/data/genshinimpact/characters/alhaitham.json +11 -10
- package/data/genshinimpact/characters/aloy.json +10 -9
- package/data/genshinimpact/characters/alyosha.json +34 -0
- package/data/genshinimpact/characters/amber.json +11 -10
- package/data/genshinimpact/characters/aratakiitto.json +11 -10
- package/data/genshinimpact/characters/arlecchino.json +11 -10
- package/data/genshinimpact/characters/baizhu.json +11 -10
- package/data/genshinimpact/characters/barbara.json +10 -9
- package/data/genshinimpact/characters/beidou.json +11 -10
- package/data/genshinimpact/characters/bennett.json +10 -9
- package/data/genshinimpact/characters/candace.json +11 -10
- package/data/genshinimpact/characters/charlotte.json +10 -9
- package/data/genshinimpact/characters/chasca.json +10 -9
- package/data/genshinimpact/characters/chevreuse.json +10 -9
- package/data/genshinimpact/characters/chiori.json +10 -9
- package/data/genshinimpact/characters/chongyun.json +11 -10
- package/data/genshinimpact/characters/citlali.json +10 -9
- package/data/genshinimpact/characters/clorinde.json +10 -9
- package/data/genshinimpact/characters/collei.json +11 -10
- package/data/genshinimpact/characters/columbina.json +34 -0
- package/data/genshinimpact/characters/cyno.json +11 -10
- package/data/genshinimpact/characters/dahlia.json +9 -8
- package/data/genshinimpact/characters/dehya.json +10 -9
- package/data/genshinimpact/characters/diluc.json +10 -9
- package/data/genshinimpact/characters/diona.json +10 -9
- package/data/genshinimpact/characters/dori.json +10 -9
- package/data/genshinimpact/characters/durin.json +10 -9
- package/data/genshinimpact/characters/emilie.json +10 -9
- package/data/genshinimpact/characters/escoffier.json +11 -10
- package/data/genshinimpact/characters/eula.json +10 -9
- package/data/genshinimpact/characters/faruzan.json +11 -10
- package/data/genshinimpact/characters/fischl.json +11 -10
- package/data/genshinimpact/characters/flins.json +34 -0
- package/data/genshinimpact/characters/freminet.json +10 -9
- package/data/genshinimpact/characters/furina.json +11 -10
- package/data/genshinimpact/characters/gaming.json +10 -9
- package/data/genshinimpact/characters/ganyu.json +10 -9
- package/data/genshinimpact/characters/gorou.json +11 -10
- package/data/genshinimpact/characters/hutao.json +11 -10
- package/data/genshinimpact/characters/iansan.json +10 -9
- package/data/genshinimpact/characters/ifa.json +34 -0
- package/data/genshinimpact/characters/illuga.json +34 -0
- package/data/genshinimpact/characters/ineffa.json +10 -9
- package/data/genshinimpact/characters/jahoda.json +34 -0
- package/data/genshinimpact/characters/jean.json +10 -9
- package/data/genshinimpact/characters/kachina.json +10 -9
- package/data/genshinimpact/characters/kaedeharakazuha.json +11 -10
- package/data/genshinimpact/characters/kaeya.json +10 -9
- package/data/genshinimpact/characters/kamisatoayaka.json +10 -9
- package/data/genshinimpact/characters/kamisatoayato.json +10 -9
- package/data/genshinimpact/characters/kaveh.json +11 -10
- package/data/genshinimpact/characters/keqing.json +10 -9
- package/data/genshinimpact/characters/kinich.json +10 -9
- package/data/genshinimpact/characters/kirara.json +10 -9
- package/data/genshinimpact/characters/klee.json +10 -9
- package/data/genshinimpact/characters/kujousara.json +10 -9
- package/data/genshinimpact/characters/kukishinobu.json +11 -10
- package/data/genshinimpact/characters/lanyan.json +10 -9
- package/data/genshinimpact/characters/lauma.json +34 -0
- package/data/genshinimpact/characters/layla.json +10 -9
- package/data/genshinimpact/characters/linnea.json +34 -0
- package/data/genshinimpact/characters/lisa.json +11 -10
- package/data/genshinimpact/characters/lohen.json +10 -9
- package/data/genshinimpact/characters/lynette.json +10 -9
- package/data/genshinimpact/characters/lyney.json +10 -9
- package/data/genshinimpact/characters/mavuika.json +10 -9
- package/data/genshinimpact/characters/mika.json +34 -0
- package/data/genshinimpact/characters/mikaschmidt.json +10 -9
- package/data/genshinimpact/characters/mona.json +11 -10
- package/data/genshinimpact/characters/mualani.json +10 -9
- package/data/genshinimpact/characters/nahida.json +11 -10
- package/data/genshinimpact/characters/navia.json +10 -9
- package/data/genshinimpact/characters/nefer.json +34 -0
- package/data/genshinimpact/characters/neuvillette.json +10 -9
- package/data/genshinimpact/characters/nicole.json +34 -0
- package/data/genshinimpact/characters/nilou.json +10 -9
- package/data/genshinimpact/characters/ningguang.json +10 -9
- package/data/genshinimpact/characters/noelle.json +11 -10
- package/data/genshinimpact/characters/odette.json +34 -0
- package/data/genshinimpact/characters/ororon.json +10 -9
- package/data/genshinimpact/characters/prune.json +9 -8
- package/data/genshinimpact/characters/qiqi.json +11 -10
- package/data/genshinimpact/characters/raidenshogun.json +11 -10
- package/data/genshinimpact/characters/razor.json +10 -9
- package/data/genshinimpact/characters/rosaria.json +11 -10
- package/data/genshinimpact/characters/sandrone.json +34 -0
- package/data/genshinimpact/characters/sangonomiyakokomi.json +11 -10
- package/data/genshinimpact/characters/sayu.json +10 -9
- package/data/genshinimpact/characters/sethos.json +10 -9
- package/data/genshinimpact/characters/shenhe.json +11 -10
- package/data/genshinimpact/characters/shikanoinheizou.json +11 -10
- package/data/genshinimpact/characters/sigewinne.json +10 -9
- package/data/genshinimpact/characters/skirk.json +10 -9
- package/data/genshinimpact/characters/sucrose.json +11 -10
- package/data/genshinimpact/characters/tartaglia.json +34 -0
- package/data/genshinimpact/characters/thoma.json +10 -9
- package/data/genshinimpact/characters/tighnari.json +11 -10
- package/data/genshinimpact/characters/traveler.json +34 -0
- package/data/genshinimpact/characters/varesa.json +10 -9
- package/data/genshinimpact/characters/varka.json +9 -8
- package/data/genshinimpact/characters/venti.json +11 -10
- package/data/genshinimpact/characters/wanderer.json +10 -9
- package/data/genshinimpact/characters/wonderlandmanekin.json +34 -0
- package/data/genshinimpact/characters/wriothesley.json +11 -10
- package/data/genshinimpact/characters/xiangling.json +11 -10
- package/data/genshinimpact/characters/xianyun.json +10 -9
- package/data/genshinimpact/characters/xiao.json +10 -9
- package/data/genshinimpact/characters/xilonen.json +10 -9
- package/data/genshinimpact/characters/xingqiu.json +11 -10
- package/data/genshinimpact/characters/xinyan.json +11 -10
- package/data/genshinimpact/characters/yaemiko.json +11 -10
- package/data/genshinimpact/characters/yanfei.json +11 -10
- package/data/genshinimpact/characters/yaoyao.json +11 -10
- package/data/genshinimpact/characters/yelan.json +10 -9
- package/data/genshinimpact/characters/yoimiya.json +11 -10
- package/data/genshinimpact/characters/yumemizukimizuki.json +10 -9
- package/data/genshinimpact/characters/yunjin.json +10 -9
- package/data/genshinimpact/characters/zhongli.json +10 -9
- package/data/genshinimpact/characters/zibai.json +10 -9
- package/data/genshinimpact/meta.json +12 -12
- package/data/honkaistarrail/characters/acheron.json +11 -10
- package/data/honkaistarrail/characters/aglaea.json +11 -10
- package/data/honkaistarrail/characters/anaxa.json +11 -10
- package/data/honkaistarrail/characters/archer.json +29 -0
- package/data/honkaistarrail/characters/argenti.json +11 -10
- package/data/honkaistarrail/characters/arlan.json +11 -10
- package/data/honkaistarrail/characters/ashveil.json +11 -10
- package/data/honkaistarrail/characters/asta.json +11 -10
- package/data/honkaistarrail/characters/aventurine.json +11 -10
- package/data/honkaistarrail/characters/bailu.json +11 -10
- package/data/honkaistarrail/characters/blackswan.json +11 -10
- package/data/honkaistarrail/characters/blade.json +11 -10
- package/data/honkaistarrail/characters/boothill.json +11 -10
- package/data/honkaistarrail/characters/bronya.json +11 -10
- package/data/honkaistarrail/characters/castorice.json +11 -10
- package/data/honkaistarrail/characters/cerydra.json +11 -10
- package/data/honkaistarrail/characters/cipher.json +11 -10
- package/data/honkaistarrail/characters/clara.json +11 -10
- package/data/honkaistarrail/characters/constance.json +11 -10
- package/data/honkaistarrail/characters/cyrene.json +11 -10
- package/data/honkaistarrail/characters/danheng.json +11 -10
- package/data/honkaistarrail/characters/danhengimbibitorlunae.json +29 -0
- package/data/honkaistarrail/characters/danhengpermansorterrae.json +29 -0
- package/data/honkaistarrail/characters/drratio.json +11 -10
- package/data/honkaistarrail/characters/evanescia.json +29 -0
- package/data/honkaistarrail/characters/evernight.json +29 -0
- package/data/honkaistarrail/characters/feixiao.json +11 -10
- package/data/honkaistarrail/characters/firefly.json +11 -10
- package/data/honkaistarrail/characters/fugue.json +29 -0
- package/data/honkaistarrail/characters/fuxuan.json +11 -10
- package/data/honkaistarrail/characters/gallagher.json +11 -10
- package/data/honkaistarrail/characters/gepard.json +11 -10
- package/data/honkaistarrail/characters/gilgamesh.json +29 -0
- package/data/honkaistarrail/characters/guinaifen.json +11 -10
- package/data/honkaistarrail/characters/hanya.json +11 -10
- package/data/honkaistarrail/characters/herta.json +11 -10
- package/data/honkaistarrail/characters/himeko.json +12 -11
- package/data/honkaistarrail/characters/himekonova.json +29 -0
- package/data/honkaistarrail/characters/hook.json +11 -10
- package/data/honkaistarrail/characters/huohuo.json +11 -10
- package/data/honkaistarrail/characters/hyacine.json +11 -10
- package/data/honkaistarrail/characters/hysilens.json +11 -10
- package/data/honkaistarrail/characters/jade.json +11 -10
- package/data/honkaistarrail/characters/jiaoqiu.json +11 -10
- package/data/honkaistarrail/characters/jingliu.json +11 -10
- package/data/honkaistarrail/characters/jingyuan.json +11 -10
- package/data/honkaistarrail/characters/kafka.json +11 -10
- package/data/honkaistarrail/characters/lingsha.json +11 -10
- package/data/honkaistarrail/characters/lisushang.json +11 -10
- package/data/honkaistarrail/characters/luka.json +11 -10
- package/data/honkaistarrail/characters/luocha.json +11 -10
- package/data/honkaistarrail/characters/lynx.json +11 -10
- package/data/honkaistarrail/characters/march7th.json +11 -10
- package/data/honkaistarrail/characters/misha.json +11 -10
- package/data/honkaistarrail/characters/mortenaxblade.json +29 -0
- package/data/honkaistarrail/characters/moze.json +11 -10
- package/data/honkaistarrail/characters/mydei.json +11 -10
- package/data/honkaistarrail/characters/natasha.json +11 -10
- package/data/honkaistarrail/characters/pela.json +11 -10
- package/data/honkaistarrail/characters/phainon.json +11 -10
- package/data/honkaistarrail/characters/qingque.json +11 -10
- package/data/honkaistarrail/characters/rappa.json +11 -10
- package/data/honkaistarrail/characters/rintohsaka.json +29 -0
- package/data/honkaistarrail/characters/robin.json +11 -10
- package/data/honkaistarrail/characters/ruanmei.json +11 -10
- package/data/honkaistarrail/characters/saber.json +29 -0
- package/data/honkaistarrail/characters/sampo.json +11 -10
- package/data/honkaistarrail/characters/seele.json +11 -10
- package/data/honkaistarrail/characters/serval.json +11 -10
- package/data/honkaistarrail/characters/silverwolf.json +11 -10
- package/data/honkaistarrail/characters/silverwolflv999.json +29 -0
- package/data/honkaistarrail/characters/sparkle.json +11 -10
- package/data/honkaistarrail/characters/sparxie.json +29 -0
- package/data/honkaistarrail/characters/sunday.json +29 -0
- package/data/honkaistarrail/characters/thedahlia.json +29 -0
- package/data/honkaistarrail/characters/theherta.json +29 -0
- package/data/honkaistarrail/characters/tingyun.json +11 -10
- package/data/honkaistarrail/characters/topaz.json +11 -10
- package/data/honkaistarrail/characters/topaznumby.json +29 -0
- package/data/honkaistarrail/characters/trailblazer.json +29 -0
- package/data/honkaistarrail/characters/tribbie.json +11 -10
- package/data/honkaistarrail/characters/welt.json +29 -0
- package/data/honkaistarrail/characters/weltyang.json +11 -10
- package/data/honkaistarrail/characters/xueyi.json +11 -10
- package/data/honkaistarrail/characters/yanqing.json +11 -10
- package/data/honkaistarrail/characters/yaoguang.json +11 -10
- package/data/honkaistarrail/characters/yukong.json +11 -10
- package/data/honkaistarrail/characters/yunli.json +11 -10
- package/data/honkaistarrail/meta.json +12 -12
- package/data/nte/characters/adler.json +13 -12
- package/data/nte/characters/aurelia.json +13 -12
- package/data/nte/characters/baicang.json +13 -12
- package/data/nte/characters/chaos.json +13 -12
- package/data/nte/characters/chiz.json +13 -12
- package/data/nte/characters/daffodill.json +13 -12
- package/data/nte/characters/edgar.json +13 -12
- package/data/nte/characters/esperzero.json +13 -12
- package/data/nte/characters/fadia.json +13 -12
- package/data/nte/characters/haniel.json +13 -12
- package/data/nte/characters/hathor.json +13 -12
- package/data/nte/characters/hotori.json +13 -12
- package/data/nte/characters/iroi.json +13 -12
- package/data/nte/characters/jiuyuan.json +13 -12
- package/data/nte/characters/lacrimosa.json +13 -12
- package/data/nte/characters/mint.json +13 -12
- package/data/nte/characters/nanally.json +13 -12
- package/data/nte/characters/sakiri.json +13 -12
- package/data/nte/characters/shinku.json +13 -12
- package/data/nte/characters/skia.json +13 -12
- package/data/nte/characters/zankou.json +51 -0
- package/data/nte/meta.json +12 -12
- package/data/toweroffantasy/characters/alyss.json +27 -0
- package/data/toweroffantasy/characters/annabella.json +23 -0
- package/data/toweroffantasy/characters/asuka.json +23 -0
- package/data/toweroffantasy/characters/bailing.json +23 -0
- package/data/toweroffantasy/characters/brevey.json +23 -0
- package/data/toweroffantasy/characters/claudia.json +23 -0
- package/data/toweroffantasy/characters/cobaltb.json +23 -0
- package/data/toweroffantasy/characters/cocoritter.json +23 -0
- package/data/toweroffantasy/characters/crow.json +23 -0
- package/data/toweroffantasy/characters/echo.json +23 -0
- package/data/toweroffantasy/characters/ene.json +23 -0
- package/data/toweroffantasy/characters/feise.json +25 -0
- package/data/toweroffantasy/characters/fenrir.json +23 -0
- package/data/toweroffantasy/characters/fiona.json +25 -0
- package/data/toweroffantasy/characters/frigg.json +23 -0
- package/data/toweroffantasy/characters/gnonno.json +23 -0
- package/data/toweroffantasy/characters/hilda.json +23 -0
- package/data/toweroffantasy/characters/huang.json +25 -0
- package/data/toweroffantasy/characters/huma.json +25 -0
- package/data/toweroffantasy/characters/icarus.json +23 -0
- package/data/toweroffantasy/characters/jiyu.json +25 -0
- package/data/toweroffantasy/characters/king.json +23 -0
- package/data/toweroffantasy/characters/lan.json +25 -0
- package/data/toweroffantasy/characters/lin.json +23 -0
- package/data/toweroffantasy/characters/linghan.json +23 -0
- package/data/toweroffantasy/characters/liuhuo.json +23 -0
- package/data/toweroffantasy/characters/lyra.json +23 -0
- package/data/toweroffantasy/characters/meryl.json +23 -0
- package/data/toweroffantasy/characters/mingjing.json +27 -0
- package/data/toweroffantasy/characters/nanyin.json +25 -0
- package/data/toweroffantasy/characters/nemesis.json +25 -0
- package/data/toweroffantasy/characters/pepper.json +23 -0
- package/data/toweroffantasy/characters/plotti.json +25 -0
- package/data/toweroffantasy/characters/roslyn.json +23 -0
- package/data/toweroffantasy/characters/rubilia.json +25 -0
- package/data/toweroffantasy/characters/ruby.json +25 -0
- package/data/toweroffantasy/characters/sakifuwa.json +23 -0
- package/data/toweroffantasy/characters/samir.json +23 -0
- package/data/toweroffantasy/characters/shiro.json +25 -0
- package/data/toweroffantasy/characters/tianlang.json +25 -0
- package/data/toweroffantasy/characters/tsubasa.json +23 -0
- package/data/toweroffantasy/characters/umi.json +23 -0
- package/data/toweroffantasy/characters/yanmiao.json +25 -0
- package/data/toweroffantasy/characters/yanuo.json +23 -0
- package/data/toweroffantasy/characters/yulan.json +23 -0
- package/data/toweroffantasy/characters/zero.json +23 -0
- package/data/vrchat/groups/clubzodiac.json +2 -1
- package/data/vrchat/groups/dirtydancers.json +2 -1
- package/data/vrchat/groups/drumsynews.json +2 -1
- package/data/vrchat/groups/ebola20.json +2 -1
- package/data/vrchat/groups/internationaldancersassociation.json +2 -1
- package/data/vrchat/groups/kannagang.json +2 -1
- package/data/vrchat/groups/lolisquad.json +2 -1
- package/data/vrchat/groups/nekonightsnightclub.json +2 -1
- package/data/vrchat/groups/nekostudios.json +2 -1
- package/data/vrchat/groups/nekosunecommunity.json +2 -1
- package/data/vrchat/groups/voidclub.json +2 -1
- package/data/vrchat/groups/voltdanceclub.json +2 -1
- package/data/vrchat/groups/vrceventsgroup.json +2 -1
- package/data/vrchat/groups/vrchatcommunitydiscord.json +2 -1
- package/data/vrchat/meta.json +11 -11
- package/data/vrchat/players/alto.json +8 -7
- package/data/vrchat/players/arcadum.json +9 -8
- package/data/vrchat/players/chairhandler.json +8 -7
- package/data/vrchat/players/chipz.json +8 -7
- package/data/vrchat/players/hikarivr.json +10 -9
- package/data/vrchat/players/kimple.json +9 -8
- package/data/vrchat/players/linkzr.json +7 -6
- package/data/vrchat/players/nagzz21.json +8 -7
- package/data/vrchat/players/nebride.json +9 -8
- package/data/vrchat/players/nekosunevr.json +10 -9
- package/data/vrchat/players/pokelawls.json +8 -7
- package/data/vrchat/players/qwonk.json +7 -6
- package/data/vrchat/players/roflgator.json +9 -8
- package/data/vrchat/players/scifri.json +6 -5
- package/data/vrchat/players/sodapoppin.json +8 -7
- package/data/vrchat/players/woops.json +8 -7
- package/data/vrchat/players/zentreya.json +9 -8
- package/data/vrchat/worlds/ascensionacademy.json +6 -5
- package/data/vrchat/worlds/clubvgl.json +7 -6
- package/data/vrchat/worlds/fightfinder.json +7 -6
- package/data/vrchat/worlds/hangouthouse.json +6 -5
- package/data/vrchat/worlds/nekostudioshalloween.json +8 -7
- package/data/vrchat/worlds/nekostudiosnewyear.json +8 -7
- package/data/vrchat/worlds/nekostudiosxmas.json +8 -7
- package/data/vrchat/worlds/nekosunevranimations2023.json +8 -7
- package/data/vrchat/worlds/nekosunevranimations20232024.json +10 -0
- package/data/vrchat/worlds/nekosunevranimations2024.json +8 -7
- package/data/vrchat/worlds/nekosunevranimations2025.json +8 -7
- package/data/vrchat/worlds/rumhamsavatarworld.json +6 -5
- package/data/vrchat/worlds/smewsranch.json +6 -5
- package/data/vrchat/worlds/smewsroom.json +6 -5
- package/data/vrchat/worlds/sunsetbar.json +6 -5
- package/data/vrchat/worlds/theblackcat.json +9 -8
- package/data/vrchat/worlds/thegreatpug.json +7 -6
- package/data/vrchat/worlds/themidnightbar.json +6 -5
- package/data/vrchat/worlds/voidclub.json +6 -5
- package/data/vrchat/worlds/voltdanceclub.json +6 -5
- package/data/vrchat/worlds/vrchathome.json +7 -6
- package/data/vrchat/worlds/whatalegend.json +5 -4
- package/data/warframe/characters/amesha.json +226 -0
- package/data/warframe/characters/ash.json +1354 -0
- package/data/warframe/characters/ashprime.json +1994 -0
- package/data/warframe/characters/atlas.json +916 -0
- package/data/warframe/characters/atlasprime.json +1632 -0
- package/data/warframe/characters/banshee.json +1036 -0
- package/data/warframe/characters/bansheeprime.json +1750 -0
- package/data/warframe/characters/baruuk.json +555 -0
- package/data/warframe/characters/baruukprime.json +1298 -0
- package/data/warframe/characters/bonewidow.json +354 -0
- package/data/warframe/characters/caliban.json +434 -0
- package/data/warframe/characters/calibanprime.json +683 -0
- package/data/warframe/characters/chroma.json +1344 -0
- package/data/warframe/characters/chromaprime.json +1977 -0
- package/data/warframe/characters/citrine.json +220 -0
- package/data/warframe/characters/cyte09.json +218 -0
- package/data/warframe/characters/dagath.json +194 -0
- package/data/warframe/characters/dante.json +223 -0
- package/data/warframe/characters/elytron.json +300 -0
- package/data/warframe/characters/ember.json +1209 -0
- package/data/warframe/characters/emberprime.json +1678 -0
- package/data/warframe/characters/equinox.json +958 -0
- package/data/warframe/characters/equinoxprime.json +1644 -0
- package/data/warframe/characters/excalibur.json +1603 -0
- package/data/warframe/characters/excaliburprime.json +1524 -0
- package/data/warframe/characters/excaliburumbra.json +397 -0
- package/data/warframe/characters/follie.json +246 -0
- package/data/warframe/characters/frost.json +1330 -0
- package/data/warframe/characters/frostprime.json +1686 -0
- package/data/warframe/characters/gara.json +925 -0
- package/data/warframe/characters/garaprime.json +1494 -0
- package/data/warframe/characters/garuda.json +851 -0
- package/data/warframe/characters/garudaprime.json +1449 -0
- package/data/warframe/characters/gauss.json +506 -0
- package/data/warframe/characters/gaussprime.json +1077 -0
- package/data/warframe/characters/grendel.json +463 -0
- package/data/warframe/characters/grendelprime.json +772 -0
- package/data/warframe/characters/gyre.json +381 -0
- package/data/warframe/characters/gyreprime.json +403 -0
- package/data/warframe/characters/harrow.json +916 -0
- package/data/warframe/characters/harrowprime.json +1700 -0
- package/data/warframe/characters/helminth.json +220 -0
- package/data/warframe/characters/hildryn.json +627 -0
- package/data/warframe/characters/hildrynprime.json +996 -0
- package/data/warframe/characters/hydroid.json +1028 -0
- package/data/warframe/characters/hydroidprime.json +1621 -0
- package/data/warframe/characters/inaros.json +976 -0
- package/data/warframe/characters/inarosprime.json +1721 -0
- package/data/warframe/characters/itzal.json +465 -0
- package/data/warframe/characters/ivara.json +1349 -0
- package/data/warframe/characters/ivaraprime.json +1921 -0
- package/data/warframe/characters/jade.json +240 -0
- package/data/warframe/characters/khora.json +804 -0
- package/data/warframe/characters/khoraprime.json +1403 -0
- package/data/warframe/characters/koumei.json +221 -0
- package/data/warframe/characters/kullervo.json +191 -0
- package/data/warframe/characters/lavos.json +511 -0
- package/data/warframe/characters/lavosprime.json +711 -0
- package/data/warframe/characters/limbo.json +1363 -0
- package/data/warframe/characters/limboprime.json +2084 -0
- package/data/warframe/characters/loki.json +1209 -0
- package/data/warframe/characters/lokiprime.json +1457 -0
- package/data/warframe/characters/mag.json +2089 -0
- package/data/warframe/characters/magprime.json +2532 -0
- package/data/warframe/characters/mesa.json +1195 -0
- package/data/warframe/characters/mesaprime.json +2059 -0
- package/data/warframe/characters/mirage.json +1561 -0
- package/data/warframe/characters/mirageprime.json +2257 -0
- package/data/warframe/characters/nekros.json +1235 -0
- package/data/warframe/characters/nekrosprime.json +1882 -0
- package/data/warframe/characters/nezha.json +868 -0
- package/data/warframe/characters/nezhaprime.json +1695 -0
- package/data/warframe/characters/nidus.json +1027 -0
- package/data/warframe/characters/nidusprime.json +1706 -0
- package/data/warframe/characters/nokko.json +308 -0
- package/data/warframe/characters/nova.json +1226 -0
- package/data/warframe/characters/novaprime.json +1667 -0
- package/data/warframe/characters/nyx.json +1610 -0
- package/data/warframe/characters/nyxprime.json +1967 -0
- package/data/warframe/characters/oberon.json +1249 -0
- package/data/warframe/characters/oberonprime.json +1872 -0
- package/data/warframe/characters/octavia.json +1049 -0
- package/data/warframe/characters/octaviaprime.json +1735 -0
- package/data/warframe/characters/odonata.json +336 -0
- package/data/warframe/characters/odonataprime.json +787 -0
- package/data/warframe/characters/oraxia.json +246 -0
- package/data/warframe/characters/orionsirius.json +134 -0
- package/data/warframe/characters/protea.json +521 -0
- package/data/warframe/characters/proteaprime.json +992 -0
- package/data/warframe/characters/qorvex.json +214 -0
- package/data/warframe/characters/revenant.json +931 -0
- package/data/warframe/characters/revenantprime.json +1494 -0
- package/data/warframe/characters/rhino.json +1344 -0
- package/data/warframe/characters/rhinoprime.json +1675 -0
- package/data/warframe/characters/saryn.json +1210 -0
- package/data/warframe/characters/sarynprime.json +2042 -0
- package/data/warframe/characters/sevagoth.json +478 -0
- package/data/warframe/characters/sevagothprime.json +910 -0
- package/data/warframe/characters/siriusorion.json +247 -0
- package/data/warframe/characters/styanax.json +320 -0
- package/data/warframe/characters/styanaxprime.json +321 -0
- package/data/warframe/characters/temple.json +271 -0
- package/data/warframe/characters/titania.json +1227 -0
- package/data/warframe/characters/titaniaprime.json +2000 -0
- package/data/warframe/characters/trinity.json +1097 -0
- package/data/warframe/characters/trinityprime.json +1849 -0
- package/data/warframe/characters/uriel.json +215 -0
- package/data/warframe/characters/valkyr.json +1165 -0
- package/data/warframe/characters/valkyrprime.json +1969 -0
- package/data/warframe/characters/vauban.json +1067 -0
- package/data/warframe/characters/vaubanprime.json +1614 -0
- package/data/warframe/characters/voidrig.json +355 -0
- package/data/warframe/characters/volt.json +1324 -0
- package/data/warframe/characters/voltprime.json +1843 -0
- package/data/warframe/characters/voruna.json +293 -0
- package/data/warframe/characters/vorunaprime.json +431 -0
- package/data/warframe/characters/wisp.json +824 -0
- package/data/warframe/characters/wispprime.json +908 -0
- package/data/warframe/characters/wukong.json +927 -0
- package/data/warframe/characters/wukongprime.json +1754 -0
- package/data/warframe/characters/xaku.json +620 -0
- package/data/warframe/characters/xakuprime.json +879 -0
- package/data/warframe/characters/yareli.json +409 -0
- package/data/warframe/characters/yareliprime.json +683 -0
- package/data/warframe/characters/zephyr.json +1149 -0
- package/data/warframe/characters/zephyrprime.json +1864 -0
- package/gamecodes.js +30 -4
- package/index.js +5 -3
- package/package.json +15 -3
- package/scripts/cache-sync-smoke.js +50 -3
- package/scripts/game-code-cache-smoke.js +46 -6
- package/scripts/sync-api-cache.js +87 -38
- package/scripts/sync-game-code-cache.js +42 -3
- package/data/game-codes/.gitkeep +0 -0
- package/data/vrchat/avatars/.gitkeep +0 -0
package/gamecodes.js
CHANGED
|
@@ -91,6 +91,31 @@ async function resolveCachedGame(game) {
|
|
|
91
91
|
return slug;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
function hydrateCachedExpiry(entry) {
|
|
95
|
+
if (!entry || typeof entry !== 'object') return entry;
|
|
96
|
+
const output = { ...entry };
|
|
97
|
+
if (!entry.expiry || typeof entry.expiry !== 'object' || Array.isArray(entry.expiry)) return output;
|
|
98
|
+
|
|
99
|
+
const expiry = { ...entry.expiry };
|
|
100
|
+
const expiresAtMs = expiry.expiresAt ? new Date(expiry.expiresAt).getTime() : NaN;
|
|
101
|
+
if (!Number.isNaN(expiresAtMs)) {
|
|
102
|
+
const remainingSeconds = Math.max(0, Math.floor((expiresAtMs - Date.now()) / 1000));
|
|
103
|
+
expiry.expiresInSeconds = remainingSeconds;
|
|
104
|
+
expiry.expired = expiresAtMs <= Date.now();
|
|
105
|
+
expiry.countdown = {
|
|
106
|
+
days: Math.floor(remainingSeconds / 86400),
|
|
107
|
+
hours: Math.floor((remainingSeconds % 86400) / 3600),
|
|
108
|
+
minutes: Math.floor((remainingSeconds % 3600) / 60),
|
|
109
|
+
seconds: remainingSeconds % 60
|
|
110
|
+
};
|
|
111
|
+
} else {
|
|
112
|
+
expiry.expiresInSeconds = null;
|
|
113
|
+
expiry.countdown = null;
|
|
114
|
+
}
|
|
115
|
+
output.expiry = expiry;
|
|
116
|
+
return output;
|
|
117
|
+
}
|
|
118
|
+
|
|
94
119
|
function filterCachedList(data, options = {}) {
|
|
95
120
|
if (!data || typeof data !== 'object') return data;
|
|
96
121
|
const includeUnknown = Boolean(options.includeUnknown);
|
|
@@ -104,11 +129,12 @@ function filterCachedList(data, options = {}) {
|
|
|
104
129
|
return true;
|
|
105
130
|
};
|
|
106
131
|
|
|
132
|
+
const hydrate = (rows) => Array.isArray(rows) ? rows.filter(matches).map(hydrateCachedExpiry) : [];
|
|
107
133
|
const result = {
|
|
108
|
-
Active:
|
|
109
|
-
Expired:
|
|
134
|
+
Active: hydrate(data.Active),
|
|
135
|
+
Expired: hydrate(data.Expired)
|
|
110
136
|
};
|
|
111
|
-
if (includeUnknown) result.Unknown =
|
|
137
|
+
if (includeUnknown) result.Unknown = hydrate(data.Unknown);
|
|
112
138
|
return result;
|
|
113
139
|
}
|
|
114
140
|
|
|
@@ -159,7 +185,7 @@ async function gameCode(game, code, options = {}) {
|
|
|
159
185
|
const wanted = normalizeCode(codeValue);
|
|
160
186
|
for (const bucket of ['Active', 'Expired', 'Unknown']) {
|
|
161
187
|
for (const entry of Array.isArray(cached[bucket]) ? cached[bucket] : []) {
|
|
162
|
-
if (normalizeCode(entry?.code) === wanted) return entry;
|
|
188
|
+
if (normalizeCode(entry?.code) === wanted) return hydrateCachedExpiry(entry);
|
|
163
189
|
}
|
|
164
190
|
}
|
|
165
191
|
return { error: `Code "${code}" not found for game "${game}".` };
|
package/index.js
CHANGED
|
@@ -31,8 +31,8 @@ function withLocalSlug(filePath, data) {
|
|
|
31
31
|
|
|
32
32
|
// Live lookups go through the NekoSuneVR V5 API first so data can be updated
|
|
33
33
|
// in real time without republishing this package. The bundled JSON under
|
|
34
|
-
// data/ is the offline fallback and is mirrored from APINODE by the
|
|
35
|
-
//
|
|
34
|
+
// data/ is the offline fallback and is mirrored from APINODE by the hourly
|
|
35
|
+
// change-watch workflow.
|
|
36
36
|
async function fetchFromApi(game, type, name) {
|
|
37
37
|
if (apiDisabled() || typeof fetch !== 'function') return null;
|
|
38
38
|
|
|
@@ -210,12 +210,14 @@ module.exports = {
|
|
|
210
210
|
Players: (game, name) => load(game, 'players', name),
|
|
211
211
|
Avatars: (game, name) => load(game, 'avatars', name),
|
|
212
212
|
Characters: (game, name) => load(game, 'characters', name),
|
|
213
|
+
NPCs: (game, name) => load(game, 'npcs', name),
|
|
214
|
+
Maps: (game, name) => load(game, 'maps', name),
|
|
213
215
|
Get: (game, type, name) => load(game, type, name),
|
|
214
216
|
List: (game, type) => list(game, type),
|
|
215
217
|
CacheInfo: () => cacheInfo(),
|
|
216
218
|
|
|
217
219
|
// Game redeem/creator code API helpers. These also use live APINODE first and
|
|
218
|
-
// automatically fall back to the
|
|
220
|
+
// automatically fall back to the hourly mirrored data/game-codes snapshot.
|
|
219
221
|
Codes: gameCodes.Codes,
|
|
220
222
|
CodesStatus: gameCodes.CodesStatus,
|
|
221
223
|
GameCodes: gameCodes.GameCodes,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nekosuneprojects/gaming-infos",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Multi-game data loader for VRChat, character datasets, and game redeem/creator codes. Uses the NekoSuneVR V5 API first with automatically mirrored local JSON fallbacks.",
|
|
3
|
+
"version": "2.4.0",
|
|
4
|
+
"description": "Multi-game data loader for VRChat, Fortnite, Call of Duty, character/NPC/map datasets, and game redeem/creator codes. Uses the NekoSuneVR V5 API first with automatically mirrored local JSON fallbacks.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "commonjs",
|
|
7
7
|
"engines": {
|
|
@@ -34,6 +34,16 @@
|
|
|
34
34
|
"wuthering-waves",
|
|
35
35
|
"warframe",
|
|
36
36
|
"fortnite",
|
|
37
|
+
"fortnite-skins",
|
|
38
|
+
"fortnite-npcs",
|
|
39
|
+
"fortnite-maps",
|
|
40
|
+
"call-of-duty",
|
|
41
|
+
"cod",
|
|
42
|
+
"modern-warfare",
|
|
43
|
+
"black-ops",
|
|
44
|
+
"call-of-duty-maps",
|
|
45
|
+
"call-of-duty-characters",
|
|
46
|
+
"call-of-duty-npcs",
|
|
37
47
|
"zenless-zone-zero",
|
|
38
48
|
"tower-of-fantasy",
|
|
39
49
|
"arknights-endfield",
|
|
@@ -41,7 +51,9 @@
|
|
|
41
51
|
"groups",
|
|
42
52
|
"players",
|
|
43
53
|
"avatars",
|
|
44
|
-
"characters"
|
|
54
|
+
"characters",
|
|
55
|
+
"npcs",
|
|
56
|
+
"maps"
|
|
45
57
|
],
|
|
46
58
|
"author": "NekoSuneVR",
|
|
47
59
|
"license": "MIT"
|
|
@@ -5,7 +5,12 @@ const fs = require('fs');
|
|
|
5
5
|
const http = require('http');
|
|
6
6
|
const os = require('os');
|
|
7
7
|
const path = require('path');
|
|
8
|
-
const {
|
|
8
|
+
const {
|
|
9
|
+
DEFAULT_MANIFEST,
|
|
10
|
+
mergeDiscoveredManifest,
|
|
11
|
+
validateRootSyncState,
|
|
12
|
+
syncApiCache
|
|
13
|
+
} = require('./sync-api-cache');
|
|
9
14
|
|
|
10
15
|
function json(res, status, body) {
|
|
11
16
|
const payload = JSON.stringify(body);
|
|
@@ -14,6 +19,40 @@ function json(res, status, body) {
|
|
|
14
19
|
}
|
|
15
20
|
|
|
16
21
|
async function main() {
|
|
22
|
+
assert.deepStrictEqual(
|
|
23
|
+
DEFAULT_MANIFEST.fortnite,
|
|
24
|
+
['characters', 'npcs', 'maps'],
|
|
25
|
+
'Fortnite fallback mirror must keep skins, NPCs, and maps as separate datasets'
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const discovered = mergeDiscoveredManifest(
|
|
29
|
+
[
|
|
30
|
+
{ slug: 'codblackops2', name: 'Call of Duty: Black Ops II', types: ['characters', 'npcs', 'maps'] },
|
|
31
|
+
{ slug: 'futuregame', name: 'Future Public Game', types: ['characters', 'maps'] }
|
|
32
|
+
],
|
|
33
|
+
{ testgame: ['characters'] }
|
|
34
|
+
);
|
|
35
|
+
assert.deepStrictEqual(discovered.testgame, ['characters']);
|
|
36
|
+
assert.deepStrictEqual(discovered.codblackops2, ['characters', 'npcs', 'maps']);
|
|
37
|
+
assert.deepStrictEqual(discovered.futuregame, ['characters', 'maps']);
|
|
38
|
+
|
|
39
|
+
assert.doesNotThrow(() => validateRootSyncState({
|
|
40
|
+
sync: {
|
|
41
|
+
running: false,
|
|
42
|
+
syncSchemaVersion: 2,
|
|
43
|
+
state: { lastStatus: 'success', syncSchemaVersion: 2, requiredSyncSchemaVersion: 2 }
|
|
44
|
+
}
|
|
45
|
+
}));
|
|
46
|
+
assert.throws(() => validateRootSyncState({ sync: { running: true, state: { lastStatus: 'running' } } }), /currently running/);
|
|
47
|
+
assert.throws(() => validateRootSyncState({ sync: { running: false, state: { lastStatus: 'failed' } } }), /state is failed/);
|
|
48
|
+
assert.throws(() => validateRootSyncState({
|
|
49
|
+
sync: {
|
|
50
|
+
running: false,
|
|
51
|
+
syncSchemaVersion: 2,
|
|
52
|
+
state: { lastStatus: 'success', syncSchemaVersion: 1, requiredSyncSchemaVersion: 2 }
|
|
53
|
+
}
|
|
54
|
+
}), /schema catch-up is incomplete/);
|
|
55
|
+
|
|
17
56
|
const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'gaming-infos-cache-test-'));
|
|
18
57
|
const dataDir = path.join(root, 'data');
|
|
19
58
|
let failCharacters = false;
|
|
@@ -24,10 +63,18 @@ async function main() {
|
|
|
24
63
|
|
|
25
64
|
const server = http.createServer((req, res) => {
|
|
26
65
|
if (req.url === '/v5/games/api/gaming-infos') {
|
|
27
|
-
return json(res, 200, {
|
|
66
|
+
return json(res, 200, {
|
|
67
|
+
success: true,
|
|
68
|
+
data: [{ slug: 'testgame', name: 'Test Game', types: ['characters'] }],
|
|
69
|
+
sync: {
|
|
70
|
+
running: false,
|
|
71
|
+
syncSchemaVersion: 2,
|
|
72
|
+
state: { lastStatus: 'success', syncSchemaVersion: 2, requiredSyncSchemaVersion: 2 }
|
|
73
|
+
}
|
|
74
|
+
});
|
|
28
75
|
}
|
|
29
76
|
if (req.url === '/v5/games/api/gaming-infos/testgame/meta/testgame') {
|
|
30
|
-
return json(res, 200, { success: true, data: { name: 'Test Game', description: 'fixture' } });
|
|
77
|
+
return json(res, 200, { success: true, data: { slug: 'testgame', name: 'Test Game', description: 'fixture', types: ['characters'] } });
|
|
31
78
|
}
|
|
32
79
|
if (req.url === '/v5/games/api/gaming-infos/testgame/characters') {
|
|
33
80
|
if (failCharacters) return json(res, 503, { success: false, error: 'fixture outage' });
|
|
@@ -18,21 +18,51 @@ async function main() {
|
|
|
18
18
|
const temp = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'game-code-cache-'));
|
|
19
19
|
const dataDir = path.join(temp, 'game-codes');
|
|
20
20
|
let phase = 1;
|
|
21
|
+
let requestTick = 0;
|
|
21
22
|
|
|
22
23
|
const directory = () => ({
|
|
23
24
|
success: true,
|
|
24
25
|
games: [
|
|
25
|
-
{ game: 'alpha', name: 'Alpha', counts: { total:
|
|
26
|
-
{ game: 'beta', name: 'Beta', counts: { total: 1 } }
|
|
26
|
+
{ game: 'alpha', name: 'Alpha', counts: { total: 2 }, lastUpdatedAt: '2026-08-24T10:00:00.000Z' },
|
|
27
|
+
{ game: 'beta', name: 'Beta', counts: { total: 1 }, lastUpdatedAt: '2026-08-24T10:05:00.000Z' }
|
|
27
28
|
],
|
|
28
|
-
aliases: { a: 'alpha', b: 'beta' }
|
|
29
|
+
aliases: { a: 'alpha', b: 'beta' },
|
|
30
|
+
// Simulates APINODE's request-time directory timestamp. This must not make
|
|
31
|
+
// an otherwise-identical fallback snapshot look changed.
|
|
32
|
+
updatedAt: new Date(Date.UTC(2026, 7, 24, 10, 10, requestTick++)).toISOString()
|
|
29
33
|
});
|
|
30
34
|
|
|
35
|
+
function expiry() {
|
|
36
|
+
return {
|
|
37
|
+
type: 'fixed',
|
|
38
|
+
expiresAt: '2030-12-31T23:59:59.000Z',
|
|
39
|
+
local: '31/12/2030, 23:59:59 UTC',
|
|
40
|
+
expired: false,
|
|
41
|
+
// These are deliberately volatile and should be stripped by the mirror.
|
|
42
|
+
expiresInSeconds: 100000 - requestTick,
|
|
43
|
+
countdown: { days: 1, hours: 2, minutes: 3, seconds: 59 - (requestTick % 50) }
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
31
47
|
const datasets = () => ({
|
|
32
48
|
alpha: phase === 1
|
|
33
|
-
? {
|
|
34
|
-
|
|
35
|
-
|
|
49
|
+
? {
|
|
50
|
+
Active: [
|
|
51
|
+
{ code: 'KEEP', status: 'active', expiry: expiry() },
|
|
52
|
+
{ code: 'REMOVE', status: 'active', expiry: expiry() }
|
|
53
|
+
],
|
|
54
|
+
Expired: [],
|
|
55
|
+
Unknown: []
|
|
56
|
+
}
|
|
57
|
+
: {
|
|
58
|
+
Active: [
|
|
59
|
+
{ code: 'KEEP', status: 'active', notes: 'changed', expiry: expiry() },
|
|
60
|
+
{ code: 'ADD', status: 'active', expiry: expiry() }
|
|
61
|
+
],
|
|
62
|
+
Expired: [],
|
|
63
|
+
Unknown: []
|
|
64
|
+
},
|
|
65
|
+
beta: { Active: [], Expired: [{ code: 'OLD', status: 'expired', expiry: expiry() }], Unknown: [] }
|
|
36
66
|
});
|
|
37
67
|
|
|
38
68
|
const originalFetch = global.fetch;
|
|
@@ -52,15 +82,25 @@ async function main() {
|
|
|
52
82
|
assert.equal(fs.existsSync(path.join(dataDir, 'games', 'alpha.json')), true);
|
|
53
83
|
let alpha = JSON.parse(await fs.promises.readFile(path.join(dataDir, 'games', 'alpha.json'), 'utf8'));
|
|
54
84
|
assert.deepEqual(alpha.Active.map((row) => row.code), ['KEEP', 'REMOVE']);
|
|
85
|
+
assert.equal(alpha.Active[0].expiry.expiresAt, '2030-12-31T23:59:59.000Z');
|
|
86
|
+
assert.equal('expiresInSeconds' in alpha.Active[0].expiry, false);
|
|
87
|
+
assert.equal('countdown' in alpha.Active[0].expiry, false);
|
|
55
88
|
|
|
56
89
|
phase = 2;
|
|
57
90
|
const second = await syncGameCodeCache({ dataDir, apiBase: 'https://example.test', apiPath: '/v5/games/api/codes', retries: 0, delayMs: 0 });
|
|
58
91
|
assert.equal(second.success, true);
|
|
92
|
+
assert.equal(second.changed, true);
|
|
59
93
|
alpha = JSON.parse(await fs.promises.readFile(path.join(dataDir, 'games', 'alpha.json'), 'utf8'));
|
|
60
94
|
assert.deepEqual(alpha.Active.map((row) => row.code), ['KEEP', 'ADD']);
|
|
61
95
|
assert.equal(alpha.Active[0].notes, 'changed');
|
|
62
96
|
assert.equal(alpha.Active.some((row) => row.code === 'REMOVE'), false);
|
|
63
97
|
|
|
98
|
+
// Same real API data, but request-time updatedAt/countdowns change again.
|
|
99
|
+
// The stable snapshot must detect this as unchanged.
|
|
100
|
+
const unchanged = await syncGameCodeCache({ dataDir, apiBase: 'https://example.test', apiPath: '/v5/games/api/codes', retries: 0, delayMs: 0 });
|
|
101
|
+
assert.equal(unchanged.success, true);
|
|
102
|
+
assert.equal(unchanged.changed, false, 'volatile countdown/request timestamps must not create cache commits');
|
|
103
|
+
|
|
64
104
|
const beforeFailure = await fs.promises.readFile(path.join(dataDir, 'games', 'alpha.json'), 'utf8');
|
|
65
105
|
phase = 3;
|
|
66
106
|
await assert.rejects(
|
|
@@ -9,9 +9,10 @@ const DEFAULT_DATA_DIR = path.join(ROOT_DIR, 'data');
|
|
|
9
9
|
const DEFAULT_API_BASE = 'https://api.nekosunevr.co.uk';
|
|
10
10
|
const DEFAULT_API_PATH = '/v5/games/api/gaming-infos';
|
|
11
11
|
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
12
|
+
// Safety baseline for older APINODE deployments. Newer APINODE root responses
|
|
13
|
+
// expose { slug, types } for every game, so the mirror automatically adds any
|
|
14
|
+
// newly published datasets (including the full Call of Duty catalog) without a
|
|
15
|
+
// package-code change. Explicit GAMING_INFOS_CACHE_MANIFEST_JSON still works.
|
|
15
16
|
const DEFAULT_MANIFEST = Object.freeze({
|
|
16
17
|
vrchat: ['players', 'groups', 'worlds', 'avatars'],
|
|
17
18
|
genshinimpact: ['characters'],
|
|
@@ -19,7 +20,7 @@ const DEFAULT_MANIFEST = Object.freeze({
|
|
|
19
20
|
nte: ['characters'],
|
|
20
21
|
wutheringwaves: ['characters'],
|
|
21
22
|
warframe: ['characters'],
|
|
22
|
-
fortnite: ['characters'],
|
|
23
|
+
fortnite: ['characters', 'npcs', 'maps'],
|
|
23
24
|
zenlesszonezero: ['characters'],
|
|
24
25
|
toweroffantasy: ['characters'],
|
|
25
26
|
arknightsendfield: ['characters']
|
|
@@ -61,11 +62,9 @@ function fallbackSlug(name) {
|
|
|
61
62
|
return safeSlug(slug, 'derived slug');
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
function
|
|
65
|
-
if (!raw) return { ...DEFAULT_MANIFEST };
|
|
66
|
-
const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
65
|
+
function normalizeManifestObject(parsed) {
|
|
67
66
|
if (!parsed || Array.isArray(parsed) || typeof parsed !== 'object') {
|
|
68
|
-
throw new Error('
|
|
67
|
+
throw new Error('Gaming Infos cache manifest must be a JSON object');
|
|
69
68
|
}
|
|
70
69
|
const result = {};
|
|
71
70
|
for (const [gameRaw, typesRaw] of Object.entries(parsed)) {
|
|
@@ -76,6 +75,23 @@ function loadManifest(raw = process.env.GAMING_INFOS_CACHE_MANIFEST_JSON) {
|
|
|
76
75
|
return result;
|
|
77
76
|
}
|
|
78
77
|
|
|
78
|
+
function loadManifest(raw = process.env.GAMING_INFOS_CACHE_MANIFEST_JSON) {
|
|
79
|
+
if (!raw) return { ...DEFAULT_MANIFEST };
|
|
80
|
+
return normalizeManifestObject(typeof raw === 'string' ? JSON.parse(raw) : raw);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function mergeDiscoveredManifest(rootData, configuredManifest = DEFAULT_MANIFEST) {
|
|
84
|
+
const result = normalizeManifestObject(configuredManifest);
|
|
85
|
+
for (const row of Array.isArray(rootData) ? rootData : []) {
|
|
86
|
+
if (!row || typeof row !== 'object' || Array.isArray(row)) continue;
|
|
87
|
+
if (!row.slug || !Array.isArray(row.types) || !row.types.length) continue;
|
|
88
|
+
const game = safeSlug(row.slug, 'root game slug');
|
|
89
|
+
const types = [...new Set(row.types.map((type) => safeSlug(type, `type for ${game}`)))];
|
|
90
|
+
if (types.length) result[game] = types;
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
|
|
79
95
|
async function requestJson(url, options = {}) {
|
|
80
96
|
const timeoutMs = numberOption(options.timeoutMs ?? process.env.GAMING_INFOS_CACHE_TIMEOUT_MS, 15000, 1000, 120000);
|
|
81
97
|
const retries = numberOption(options.retries ?? process.env.GAMING_INFOS_CACHE_RETRIES, 2, 0, 5);
|
|
@@ -104,6 +120,7 @@ async function requestJson(url, options = {}) {
|
|
|
104
120
|
if (!body || body.success === false) {
|
|
105
121
|
throw new Error(`API returned an unsuccessful response for ${url}`);
|
|
106
122
|
}
|
|
123
|
+
if (options.rawEnvelope) return body;
|
|
107
124
|
return body.data !== undefined ? body.data : body;
|
|
108
125
|
} catch (error) {
|
|
109
126
|
lastError = error;
|
|
@@ -123,6 +140,26 @@ function apiUrl(base, apiPath, suffix = '') {
|
|
|
123
140
|
return `${normalizeBase(base)}${normalizeApiPath(apiPath)}${suffix}`;
|
|
124
141
|
}
|
|
125
142
|
|
|
143
|
+
function validateRootSyncState(rootEnvelope) {
|
|
144
|
+
const sync = rootEnvelope && typeof rootEnvelope === 'object' ? rootEnvelope.sync : null;
|
|
145
|
+
if (!sync || typeof sync !== 'object') return;
|
|
146
|
+
|
|
147
|
+
const state = sync.state && typeof sync.state === 'object' ? sync.state : null;
|
|
148
|
+
const status = String(state?.lastStatus || '').toLowerCase();
|
|
149
|
+
if (sync.running || status === 'running') {
|
|
150
|
+
throw new Error('APINODE content synchronization is currently running; preserving the previous fallback until it is idle');
|
|
151
|
+
}
|
|
152
|
+
if (state && ['never-run', 'failed'].includes(status)) {
|
|
153
|
+
throw new Error(`APINODE content synchronization state is ${status}; preserving the previous fallback`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const persistedSchema = Number(state?.syncSchemaVersion || 0);
|
|
157
|
+
const requiredSchema = Number(state?.requiredSyncSchemaVersion || sync.syncSchemaVersion || 0);
|
|
158
|
+
if (requiredSchema > 0 && persistedSchema < requiredSchema) {
|
|
159
|
+
throw new Error(`APINODE content schema catch-up is incomplete (${persistedSchema}/${requiredSchema}); preserving the previous fallback`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
126
163
|
async function writeJson(filePath, value) {
|
|
127
164
|
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
|
|
128
165
|
await fs.promises.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
@@ -196,40 +233,48 @@ function normalizedEntry(item) {
|
|
|
196
233
|
}
|
|
197
234
|
|
|
198
235
|
async function buildNextSnapshot(options) {
|
|
199
|
-
const { apiBase, apiPath, dataDir, manifest } = options;
|
|
236
|
+
const { apiBase, apiPath, dataDir, manifest: configuredManifest } = options;
|
|
200
237
|
const parentDir = path.dirname(dataDir);
|
|
201
238
|
const nextDir = path.join(parentDir, `.gaming-infos-cache-next-${process.pid}-${Date.now()}`);
|
|
202
239
|
await fs.promises.rm(nextDir, { recursive: true, force: true });
|
|
203
240
|
await fs.promises.mkdir(nextDir, { recursive: true });
|
|
204
241
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
242
|
+
try {
|
|
243
|
+
const rootEnvelope = await requestJson(apiUrl(apiBase, apiPath), { ...options, rawEnvelope: true });
|
|
244
|
+
validateRootSyncState(rootEnvelope);
|
|
245
|
+
const rootData = rootEnvelope.data !== undefined ? rootEnvelope.data : rootEnvelope;
|
|
246
|
+
if (!Array.isArray(rootData) || !rootData.length) {
|
|
247
|
+
throw new Error('Gaming-infos root endpoint returned no games; refusing to replace the fallback cache');
|
|
248
|
+
}
|
|
249
|
+
const manifest = mergeDiscoveredManifest(rootData, configuredManifest);
|
|
250
|
+
|
|
251
|
+
const counts = {};
|
|
252
|
+
for (const [game, types] of Object.entries(manifest)) {
|
|
253
|
+
const meta = await requestJson(apiUrl(apiBase, apiPath, `/${encodeURIComponent(game)}/meta/${encodeURIComponent(game)}`), options);
|
|
254
|
+
if (!meta || Array.isArray(meta) || typeof meta !== 'object') throw new Error(`Invalid meta payload for ${game}`);
|
|
255
|
+
await writeJson(path.join(nextDir, game, 'meta.json'), meta);
|
|
256
|
+
counts[game] = { meta: 1 };
|
|
257
|
+
|
|
258
|
+
for (const type of types) {
|
|
259
|
+
const list = await requestJson(apiUrl(apiBase, apiPath, `/${encodeURIComponent(game)}/${encodeURIComponent(type)}`), options);
|
|
260
|
+
if (!Array.isArray(list)) throw new Error(`Expected an array for ${game}/${type}`);
|
|
261
|
+
|
|
262
|
+
const seen = new Set();
|
|
263
|
+
counts[game][type] = list.length;
|
|
264
|
+
for (const rawItem of list) {
|
|
265
|
+
const item = normalizedEntry(rawItem);
|
|
266
|
+
if (seen.has(item.slug)) throw new Error(`Duplicate slug ${item.slug} in ${game}/${type}`);
|
|
267
|
+
seen.add(item.slug);
|
|
268
|
+
await writeJson(path.join(nextDir, game, type, `${item.slug}.json`), item);
|
|
269
|
+
}
|
|
228
270
|
}
|
|
229
271
|
}
|
|
230
|
-
}
|
|
231
272
|
|
|
232
|
-
|
|
273
|
+
return { nextDir, rootGameCount: rootData.length, counts, manifest };
|
|
274
|
+
} catch (error) {
|
|
275
|
+
await fs.promises.rm(nextDir, { recursive: true, force: true }).catch(() => {});
|
|
276
|
+
throw error;
|
|
277
|
+
}
|
|
233
278
|
}
|
|
234
279
|
|
|
235
280
|
async function syncApiCache(options = {}) {
|
|
@@ -251,6 +296,7 @@ async function syncApiCache(options = {}) {
|
|
|
251
296
|
try {
|
|
252
297
|
const built = await buildNextSnapshot(requestOptions);
|
|
253
298
|
nextDir = built.nextDir;
|
|
299
|
+
const effectiveManifest = built.manifest;
|
|
254
300
|
const oldFiles = await walkFiles(dataDir);
|
|
255
301
|
const newFiles = await walkFiles(nextDir);
|
|
256
302
|
const oldHash = hashFiles(oldFiles);
|
|
@@ -264,19 +310,20 @@ async function syncApiCache(options = {}) {
|
|
|
264
310
|
changed: false,
|
|
265
311
|
contentHash: newHash,
|
|
266
312
|
source: apiUrl(apiBase, apiPath),
|
|
267
|
-
games: Object.keys(
|
|
313
|
+
games: Object.keys(effectiveManifest),
|
|
268
314
|
counts: built.counts,
|
|
269
315
|
stats
|
|
270
316
|
};
|
|
271
317
|
}
|
|
272
318
|
|
|
273
319
|
const cacheManifest = {
|
|
274
|
-
schemaVersion:
|
|
320
|
+
schemaVersion: 2,
|
|
275
321
|
source: apiUrl(apiBase, apiPath),
|
|
276
322
|
syncedAt: new Date().toISOString(),
|
|
277
323
|
contentHash: newHash,
|
|
278
324
|
rootGameCount: built.rootGameCount,
|
|
279
|
-
|
|
325
|
+
autoDiscoveredTypes: true,
|
|
326
|
+
games: Object.fromEntries(Object.entries(effectiveManifest).map(([game, types]) => [game, { types, counts: built.counts[game] }])),
|
|
280
327
|
stats
|
|
281
328
|
};
|
|
282
329
|
await writeJson(path.join(nextDir, '.cache-manifest.json'), cacheManifest);
|
|
@@ -288,7 +335,7 @@ async function syncApiCache(options = {}) {
|
|
|
288
335
|
changed: true,
|
|
289
336
|
contentHash: newHash,
|
|
290
337
|
source: cacheManifest.source,
|
|
291
|
-
games: Object.keys(
|
|
338
|
+
games: Object.keys(effectiveManifest),
|
|
292
339
|
counts: built.counts,
|
|
293
340
|
stats
|
|
294
341
|
};
|
|
@@ -313,6 +360,8 @@ if (require.main === module) {
|
|
|
313
360
|
module.exports = {
|
|
314
361
|
DEFAULT_MANIFEST,
|
|
315
362
|
loadManifest,
|
|
363
|
+
mergeDiscoveredManifest,
|
|
364
|
+
validateRootSyncState,
|
|
316
365
|
normalizedEntry,
|
|
317
366
|
snapshotHash,
|
|
318
367
|
syncApiCache
|
|
@@ -145,16 +145,51 @@ function validateCodeList(game, payload) {
|
|
|
145
145
|
return payload;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
function stableDirectorySnapshot(directory) {
|
|
149
|
+
const stable = JSON.parse(JSON.stringify(directory));
|
|
150
|
+
const updatedTimes = (stable.games || [])
|
|
151
|
+
.map((game) => new Date(game?.lastUpdatedAt || 0).getTime())
|
|
152
|
+
.filter((value) => Number.isFinite(value) && value > 0);
|
|
153
|
+
|
|
154
|
+
// APINODE's root `updatedAt` describes when the HTTP directory response was
|
|
155
|
+
// generated, so it changes every request. Keep a meaningful stable timestamp
|
|
156
|
+
// based on actual per-game data updates instead.
|
|
157
|
+
if (updatedTimes.length) stable.updatedAt = new Date(Math.max(...updatedTimes)).toISOString();
|
|
158
|
+
else delete stable.updatedAt;
|
|
159
|
+
return stable;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function stableCodeRow(row) {
|
|
163
|
+
const stable = JSON.parse(JSON.stringify(row));
|
|
164
|
+
if (stable.expiry && typeof stable.expiry === 'object' && !Array.isArray(stable.expiry)) {
|
|
165
|
+
// These values are calculated from Date.now() on every API request. The
|
|
166
|
+
// fallback retains expiresAt/local/expired, while consumers can calculate
|
|
167
|
+
// a live countdown themselves when needed.
|
|
168
|
+
delete stable.expiry.expiresInSeconds;
|
|
169
|
+
delete stable.expiry.countdown;
|
|
170
|
+
}
|
|
171
|
+
return stable;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function stableCodePayload(payload) {
|
|
175
|
+
const stable = {};
|
|
176
|
+
for (const bucket of ['Active', 'Expired', 'Unknown']) {
|
|
177
|
+
if (Array.isArray(payload[bucket])) stable[bucket] = payload[bucket].map(stableCodeRow);
|
|
178
|
+
}
|
|
179
|
+
return stable;
|
|
180
|
+
}
|
|
181
|
+
|
|
148
182
|
async function buildNextSnapshot(options) {
|
|
149
183
|
const { apiBase, apiPath, dataDir } = options;
|
|
150
184
|
const nextDir = path.join(path.dirname(dataDir), `.game-codes-cache-next-${process.pid}-${Date.now()}`);
|
|
151
185
|
await fs.promises.rm(nextDir, { recursive: true, force: true });
|
|
152
186
|
await fs.promises.mkdir(nextDir, { recursive: true });
|
|
153
187
|
|
|
154
|
-
const
|
|
155
|
-
if (!Array.isArray(
|
|
188
|
+
const rawDirectory = await requestJson(apiUrl(apiBase, apiPath), options);
|
|
189
|
+
if (!Array.isArray(rawDirectory.games) || !rawDirectory.games.length) {
|
|
156
190
|
throw new Error('Game code directory returned no games; refusing to replace fallback cache');
|
|
157
191
|
}
|
|
192
|
+
const directory = stableDirectorySnapshot(rawDirectory);
|
|
158
193
|
await writeJson(path.join(nextDir, 'directory.json'), directory);
|
|
159
194
|
|
|
160
195
|
// /status is cache-only on APINODE and does not trigger source website requests.
|
|
@@ -167,10 +202,11 @@ async function buildNextSnapshot(options) {
|
|
|
167
202
|
const delayMs = numberOption(options.delayMs ?? process.env.GAME_CODES_CACHE_GAME_DELAY_MS, 150, 0, 10000);
|
|
168
203
|
for (let i = 0; i < directory.games.length; i += 1) {
|
|
169
204
|
const game = gameSlug(directory.games[i]);
|
|
170
|
-
const
|
|
205
|
+
const rawPayload = validateCodeList(
|
|
171
206
|
game,
|
|
172
207
|
await requestJson(apiUrl(apiBase, apiPath, `/${encodeURIComponent(game)}`, { includeUnknown: 'true' }), options)
|
|
173
208
|
);
|
|
209
|
+
const payload = stableCodePayload(rawPayload);
|
|
174
210
|
await writeJson(path.join(nextDir, 'games', `${game}.json`), payload);
|
|
175
211
|
counts[game] = {
|
|
176
212
|
active: payload.Active.length,
|
|
@@ -214,6 +250,7 @@ async function syncGameCodeCache(options = {}) {
|
|
|
214
250
|
gameCount: built.directory.games.length,
|
|
215
251
|
counts: built.counts,
|
|
216
252
|
statusIsCacheOnly: true,
|
|
253
|
+
volatileFieldsRemoved: ['directory.updatedAt(request-time)', 'expiry.expiresInSeconds', 'expiry.countdown'],
|
|
217
254
|
stats
|
|
218
255
|
};
|
|
219
256
|
await writeJson(path.join(nextDir, '.cache-manifest.json'), manifest);
|
|
@@ -239,6 +276,8 @@ if (require.main === module) {
|
|
|
239
276
|
}
|
|
240
277
|
|
|
241
278
|
module.exports = {
|
|
279
|
+
stableDirectorySnapshot,
|
|
280
|
+
stableCodePayload,
|
|
242
281
|
syncGameCodeCache,
|
|
243
282
|
validateCodeList
|
|
244
283
|
};
|
package/data/game-codes/.gitkeep
DELETED
|
File without changes
|
|
File without changes
|