@nekosuneprojects/gaming-infos 2.1.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.
Files changed (722) hide show
  1. package/.github/workflows/sync-api-cache.yml +73 -0
  2. package/CACHE_SYNC.md +202 -0
  3. package/CHANGELOG.md +54 -2
  4. package/README.md +304 -136
  5. package/data/.cache-manifest.json +112 -0
  6. package/data/arknightsendfield/characters/akekuri.json +75 -0
  7. package/data/arknightsendfield/characters/alesh.json +75 -0
  8. package/data/arknightsendfield/characters/antal.json +75 -0
  9. package/data/arknightsendfield/characters/arcane.json +75 -0
  10. package/data/arknightsendfield/characters/arclight.json +75 -0
  11. package/data/arknightsendfield/characters/ardelia.json +75 -0
  12. package/data/arknightsendfield/characters/avywenna.json +76 -0
  13. package/data/arknightsendfield/characters/camille.json +77 -0
  14. package/data/arknightsendfield/characters/catcher.json +76 -0
  15. package/data/arknightsendfield/characters/chenqianyu.json +76 -0
  16. package/data/arknightsendfield/characters/dapan.json +76 -0
  17. package/data/arknightsendfield/characters/ember.json +76 -0
  18. package/data/arknightsendfield/characters/endministrator.json +63 -0
  19. package/data/arknightsendfield/characters/estella.json +76 -0
  20. package/data/arknightsendfield/characters/fluorite.json +75 -0
  21. package/data/arknightsendfield/characters/gilberta.json +76 -0
  22. package/data/arknightsendfield/characters/laevatain.json +76 -0
  23. package/data/arknightsendfield/characters/lastrite.json +76 -0
  24. package/data/arknightsendfield/characters/lifeng.json +75 -0
  25. package/data/arknightsendfield/characters/liino.json +76 -0
  26. package/data/arknightsendfield/characters/mifu.json +75 -0
  27. package/data/arknightsendfield/characters/perlica.json +76 -0
  28. package/data/arknightsendfield/characters/pogranichnik.json +75 -0
  29. package/data/arknightsendfield/characters/purrchena.json +50 -0
  30. package/data/arknightsendfield/characters/rossi.json +77 -0
  31. package/data/arknightsendfield/characters/snowshine.json +75 -0
  32. package/data/arknightsendfield/characters/tangtang.json +76 -0
  33. package/data/arknightsendfield/characters/typhoeus.json +57 -0
  34. package/data/arknightsendfield/characters/wulfgard.json +77 -0
  35. package/data/arknightsendfield/characters/xaihi.json +76 -0
  36. package/data/arknightsendfield/characters/yvonne.json +76 -0
  37. package/data/arknightsendfield/characters/zhuangfangyi.json +76 -0
  38. package/data/arknightsendfield/meta.json +14 -0
  39. package/data/fortnite/characters/agentjones.json +43 -0
  40. package/data/fortnite/characters/agentpeely.json +43 -0
  41. package/data/fortnite/characters/andyfangerson.json +42 -0
  42. package/data/fortnite/characters/antonia.json +43 -0
  43. package/data/fortnite/characters/anwar.json +44 -0
  44. package/data/fortnite/characters/aphrodite.json +43 -0
  45. package/data/fortnite/characters/apollo.json +36 -0
  46. package/data/fortnite/characters/arcticassassin.json +43 -0
  47. package/data/fortnite/characters/artemis.json +36 -0
  48. package/data/fortnite/characters/battlewoodboulevard.json +43 -0
  49. package/data/fortnite/characters/beastmode.json +45 -0
  50. package/data/fortnite/characters/bogstick.json +43 -0
  51. package/data/fortnite/characters/boxy.json +44 -0
  52. package/data/fortnite/characters/bravoleader.json +44 -0
  53. package/data/fortnite/characters/briteraider.json +43 -0
  54. package/data/fortnite/characters/brutalboxcars.json +46 -0
  55. package/data/fortnite/characters/bryne.json +44 -0
  56. package/data/fortnite/characters/buildersbarracks.json +38 -0
  57. package/data/fortnite/characters/bullshark.json +43 -0
  58. package/data/fortnite/characters/bumpybay.json +44 -0
  59. package/data/fortnite/characters/burd.json +44 -0
  60. package/data/fortnite/characters/burdemart.json +39 -0
  61. package/data/fortnite/characters/burdtogo.json +42 -0
  62. package/data/fortnite/characters/busansanctuary.json +39 -0
  63. package/data/fortnite/characters/calamaricanyon.json +41 -0
  64. package/data/fortnite/characters/canyoncrossing.json +45 -0
  65. package/data/fortnite/characters/captainjones.json +40 -0
  66. package/data/fortnite/characters/centurion.json +44 -0
  67. package/data/fortnite/characters/cerberus.json +43 -0
  68. package/data/fortnite/characters/charon.json +43 -0
  69. package/data/fortnite/characters/chiara.json +43 -0
  70. package/data/fortnite/characters/clara.json +43 -0
  71. package/data/fortnite/characters/classifiedcanyon.json +43 -0
  72. package/data/fortnite/characters/clawsylodge.json +41 -0
  73. package/data/fortnite/characters/cletuscornhole.json +43 -0
  74. package/data/fortnite/characters/contractgiller.json +44 -0
  75. package/data/fortnite/characters/corruptioncorners.json +42 -0
  76. package/data/fortnite/characters/countessdaraku.json +45 -0
  77. package/data/fortnite/characters/crackshotscabin.json +40 -0
  78. package/data/fortnite/characters/creepycamps.json +36 -0
  79. package/data/fortnite/characters/crimecity.json +47 -0
  80. package/data/fortnite/characters/dara.json +42 -0
  81. package/data/fortnite/characters/darkruby.json +43 -0
  82. package/data/fortnite/characters/demonsdojo.json +46 -0
  83. package/data/fortnite/characters/diamonddiva.json +43 -0
  84. package/data/fortnite/characters/donutdistrict.json +43 -0
  85. package/data/fortnite/characters/drakonsteelrider.json +43 -0
  86. package/data/fortnite/characters/dummy.json +45 -0
  87. package/data/fortnite/characters/edwardscissorhands.json +39 -0
  88. package/data/fortnite/characters/etheria.json +43 -0
  89. package/data/fortnite/characters/evergreenterrace.json +41 -0
  90. package/data/fortnite/characters/fishmarket.json +39 -0
  91. package/data/fortnite/characters/fishstick.json +45 -0
  92. package/data/fortnite/characters/fishthicc.json +43 -0
  93. package/data/fortnite/characters/fortressofsolitude.json +37 -0
  94. package/data/fortnite/characters/foxyfloodgate.json +43 -0
  95. package/data/fortnite/characters/freakyfields.json +38 -0
  96. package/data/fortnite/characters/goldengrove.json +41 -0
  97. package/data/fortnite/characters/graveheart.json +43 -0
  98. package/data/fortnite/characters/grievouscampsite.json +38 -0
  99. package/data/fortnite/characters/guardianamara.json +44 -0
  100. package/data/fortnite/characters/gwenpool.json +41 -0
  101. package/data/fortnite/characters/hanamuracourtyard.json +27 -0
  102. package/data/fortnite/characters/heartbreakranger.json +42 -0
  103. package/data/fortnite/characters/heatwaveharbor.json +41 -0
  104. package/data/fortnite/characters/heroichope.json +44 -0
  105. package/data/fortnite/characters/hope.json +43 -0
  106. package/data/fortnite/characters/hopefulheights.json +46 -0
  107. package/data/fortnite/characters/humblehills.json +41 -0
  108. package/data/fortnite/characters/innovatorslone.json +43 -0
  109. package/data/fortnite/characters/insight.json +43 -0
  110. package/data/fortnite/characters/ironman.json +40 -0
  111. package/data/fortnite/characters/jittershock.json +44 -0
  112. package/data/fortnite/characters/joeycaliente.json +44 -0
  113. package/data/fortnite/characters/jonithescarlet.json +43 -0
  114. package/data/fortnite/characters/khabylame.json +40 -0
  115. package/data/fortnite/characters/khari.json +44 -0
  116. package/data/fortnite/characters/kitbash.json +44 -0
  117. package/data/fortnite/characters/kitesflight.json +41 -0
  118. package/data/fortnite/characters/knightmare.json +43 -0
  119. package/data/fortnite/characters/laguna.json +43 -0
  120. package/data/fortnite/characters/lakespringfield.json +35 -0
  121. package/data/fortnite/characters/lakespringfieldgasstation.json +39 -0
  122. package/data/fortnite/characters/lanallane.json +42 -0
  123. package/data/fortnite/characters/lattelanding.json +41 -0
  124. package/data/fortnite/characters/lavendra.json +44 -0
  125. package/data/fortnite/characters/leelah.json +45 -0
  126. package/data/fortnite/characters/liftylodge.json +42 -0
  127. package/data/fortnite/characters/lonewolflair.json +42 -0
  128. package/data/fortnite/characters/longshot.json +41 -0
  129. package/data/fortnite/characters/lostlake.json +44 -0
  130. package/data/fortnite/characters/loveranger.json +44 -0
  131. package/data/fortnite/characters/mae.json +43 -0
  132. package/data/fortnite/characters/magicmosses.json +42 -0
  133. package/data/fortnite/characters/maskedmeadows.json +48 -0
  134. package/data/fortnite/characters/mechateamshadow.json +44 -0
  135. package/data/fortnite/characters/medusa.json +43 -0
  136. package/data/fortnite/characters/meowscles.json +42 -0
  137. package/data/fortnite/characters/mephisto.json +41 -0
  138. package/data/fortnite/characters/metalmouth.json +44 -0
  139. package/data/fortnite/characters/mincemeat.json +43 -0
  140. package/data/fortnite/characters/munitionsexpert.json +42 -0
  141. package/data/fortnite/characters/myna.json +42 -0
  142. package/data/fortnite/characters/newsanctuary.json +43 -0
  143. package/data/fortnite/characters/nia.json +42 -0
  144. package/data/fortnite/characters/nightshiftforest.json +43 -0
  145. package/data/fortnite/characters/nisha.json +42 -0
  146. package/data/fortnite/characters/nolanchance.json +43 -0
  147. package/data/fortnite/characters/noorah.json +44 -0
  148. package/data/fortnite/characters/odyssey.json +43 -0
  149. package/data/fortnite/characters/openaironsen.json +40 -0
  150. package/data/fortnite/characters/oscar.json +42 -0
  151. package/data/fortnite/characters/outlawoasis.json +40 -0
  152. package/data/fortnite/characters/outpostenclave.json +27 -0
  153. package/data/fortnite/characters/oxrhq.json +37 -0
  154. package/data/fortnite/characters/paintedpalms.json +41 -0
  155. package/data/fortnite/characters/peabody.json +42 -0
  156. package/data/fortnite/characters/peelosopherbananocrates.json +43 -0
  157. package/data/fortnite/characters/peely.json +41 -0
  158. package/data/fortnite/characters/peelybone.json +43 -0
  159. package/data/fortnite/characters/persephone.json +42 -0
  160. package/data/fortnite/characters/phaedra.json +42 -0
  161. package/data/fortnite/characters/phantommeowscles.json +43 -0
  162. package/data/fortnite/characters/piperpace.json +43 -0
  163. package/data/fortnite/characters/poseidon.json +42 -0
  164. package/data/fortnite/characters/pumpedpower.json +40 -0
  165. package/data/fortnite/characters/pumpinpipes.json +39 -0
  166. package/data/fortnite/characters/pumpkinking.json +40 -0
  167. package/data/fortnite/characters/purradisemeowscles.json +43 -0
  168. package/data/fortnite/characters/rangersrest.json +36 -0
  169. package/data/fortnite/characters/rebeloutpost.json +37 -0
  170. package/data/fortnite/characters/renegadeshadow.json +44 -0
  171. package/data/fortnite/characters/resistancebase.json +27 -0
  172. package/data/fortnite/characters/rippedtides.json +43 -0
  173. package/data/fortnite/characters/rollingblossomsfarm.json +41 -0
  174. package/data/fortnite/characters/royalebomber.json +38 -0
  175. package/data/fortnite/characters/ruckus.json +42 -0
  176. package/data/fortnite/characters/rust.json +41 -0
  177. package/data/fortnite/characters/sakuracircle.json +36 -0
  178. package/data/fortnite/characters/sandystrip.json +44 -0
  179. package/data/fortnite/characters/saskia.json +45 -0
  180. package/data/fortnite/characters/seaportcity.json +48 -0
  181. package/data/fortnite/characters/shakensanctuary.json +42 -0
  182. package/data/fortnite/characters/shimmerdusk.json +41 -0
  183. package/data/fortnite/characters/shinyshafts.json +38 -0
  184. package/data/fortnite/characters/shogunssolitude.json +46 -0
  185. package/data/fortnite/characters/shuri.json +39 -0
  186. package/data/fortnite/characters/silashesk.json +42 -0
  187. package/data/fortnite/characters/slurproom.json +40 -0
  188. package/data/fortnite/characters/snowheart.json +44 -0
  189. package/data/fortnite/characters/snowstriker.json +43 -0
  190. package/data/fortnite/characters/solidsnake.json +39 -0
  191. package/data/fortnite/characters/sonor.json +46 -0
  192. package/data/fortnite/characters/spartanassassin.json +43 -0
  193. package/data/fortnite/characters/spirithuntersaeko.json +41 -0
  194. package/data/fortnite/characters/springfieldnationalparkstore.json +38 -0
  195. package/data/fortnite/characters/springfieldnuclearpowerplant.json +39 -0
  196. package/data/fortnite/characters/springfieldslurpworks.json +40 -0
  197. package/data/fortnite/characters/squibblyshores.json +42 -0
  198. package/data/fortnite/characters/squibblyspointlighthouse.json +37 -0
  199. package/data/fortnite/characters/starroakley.json +45 -0
  200. package/data/fortnite/characters/steelsight.json +43 -0
  201. package/data/fortnite/characters/sticks.json +35 -0
  202. package/data/fortnite/characters/sunkenshores.json +42 -0
  203. package/data/fortnite/characters/sunstrider.json +42 -0
  204. package/data/fortnite/characters/suntanspecialist.json +43 -0
  205. package/data/fortnite/characters/supernovaacademy.json +27 -0
  206. package/data/fortnite/characters/t60powerarmor.json +39 -0
  207. package/data/fortnite/characters/thebattlewoods.json +44 -0
  208. package/data/fortnite/characters/thegreatturtle.json +37 -0
  209. package/data/fortnite/characters/theoperator.json +42 -0
  210. package/data/fortnite/characters/thequackaway.json +39 -0
  211. package/data/fortnite/characters/thestabilizer.json +38 -0
  212. package/data/fortnite/characters/theunderwriter.json +36 -0
  213. package/data/fortnite/characters/toyboxdocks.json +40 -0
  214. package/data/fortnite/characters/trace.json +43 -0
  215. package/data/fortnite/characters/triagetrooper.json +43 -0
  216. package/data/fortnite/characters/triggerfish.json +44 -0
  217. package/data/fortnite/characters/trogscave.json +38 -0
  218. package/data/fortnite/characters/utopiacity.json +27 -0
  219. package/data/fortnite/characters/vengeancejones.json +42 -0
  220. package/data/fortnite/characters/victoriasaint.json +43 -0
  221. package/data/fortnite/characters/vineyshafts.json +35 -0
  222. package/data/fortnite/characters/volpez.json +41 -0
  223. package/data/fortnite/characters/warmachine.json +40 -0
  224. package/data/fortnite/characters/wastelandermagneto.json +40 -0
  225. package/data/fortnite/characters/wonkeeland.json +41 -0
  226. package/data/fortnite/meta.json +14 -0
  227. package/data/game-codes/.cache-manifest.json +148 -0
  228. package/data/game-codes/directory.json +1196 -0
  229. package/data/game-codes/games/arknights-endfield.json +958 -0
  230. package/data/game-codes/games/borderlands-1.json +414 -0
  231. package/data/game-codes/games/borderlands-2.json +340 -0
  232. package/data/game-codes/games/borderlands-3.json +1025 -0
  233. package/data/game-codes/games/borderlands-4.json +3128 -0
  234. package/data/game-codes/games/borderlands-the-pre-sequel.json +599 -0
  235. package/data/game-codes/games/brawl-stars.json +4766 -0
  236. package/data/game-codes/games/clash-of-clans.json +4766 -0
  237. package/data/game-codes/games/clash-royale.json +5 -0
  238. package/data/game-codes/games/cookie-run-kingdom.json +5366 -0
  239. package/data/game-codes/games/fortnite.json +897 -0
  240. package/data/game-codes/games/genshin-impact.json +8520 -0
  241. package/data/game-codes/games/honkai-impact-3rd.json +405 -0
  242. package/data/game-codes/games/honkai-star-rail.json +9863 -0
  243. package/data/game-codes/games/neverness-to-everness.json +1695 -0
  244. package/data/game-codes/games/roblox-doors.json +1842 -0
  245. package/data/game-codes/games/roblox-rivals.json +2919 -0
  246. package/data/game-codes/games/tower-of-fantasy.json +2839 -0
  247. package/data/game-codes/games/warframe.json +20824 -0
  248. package/data/game-codes/games/wuthering-waves.json +2477 -0
  249. package/data/game-codes/games/zenless-zone-zero.json +3681 -0
  250. package/data/game-codes/status.json +580 -0
  251. package/data/genshinimpact/characters/aino.json +34 -0
  252. package/data/genshinimpact/characters/albedo.json +10 -9
  253. package/data/genshinimpact/characters/alhaitham.json +11 -10
  254. package/data/genshinimpact/characters/aloy.json +10 -9
  255. package/data/genshinimpact/characters/alyosha.json +34 -0
  256. package/data/genshinimpact/characters/amber.json +11 -10
  257. package/data/genshinimpact/characters/aratakiitto.json +11 -10
  258. package/data/genshinimpact/characters/arlecchino.json +11 -10
  259. package/data/genshinimpact/characters/baizhu.json +11 -10
  260. package/data/genshinimpact/characters/barbara.json +10 -9
  261. package/data/genshinimpact/characters/beidou.json +11 -10
  262. package/data/genshinimpact/characters/bennett.json +10 -9
  263. package/data/genshinimpact/characters/candace.json +11 -10
  264. package/data/genshinimpact/characters/charlotte.json +10 -9
  265. package/data/genshinimpact/characters/chasca.json +10 -9
  266. package/data/genshinimpact/characters/chevreuse.json +10 -9
  267. package/data/genshinimpact/characters/chiori.json +10 -9
  268. package/data/genshinimpact/characters/chongyun.json +11 -10
  269. package/data/genshinimpact/characters/citlali.json +10 -9
  270. package/data/genshinimpact/characters/clorinde.json +10 -9
  271. package/data/genshinimpact/characters/collei.json +11 -10
  272. package/data/genshinimpact/characters/columbina.json +34 -0
  273. package/data/genshinimpact/characters/cyno.json +11 -10
  274. package/data/genshinimpact/characters/dahlia.json +9 -8
  275. package/data/genshinimpact/characters/dehya.json +10 -9
  276. package/data/genshinimpact/characters/diluc.json +10 -9
  277. package/data/genshinimpact/characters/diona.json +10 -9
  278. package/data/genshinimpact/characters/dori.json +10 -9
  279. package/data/genshinimpact/characters/durin.json +10 -9
  280. package/data/genshinimpact/characters/emilie.json +10 -9
  281. package/data/genshinimpact/characters/escoffier.json +11 -10
  282. package/data/genshinimpact/characters/eula.json +10 -9
  283. package/data/genshinimpact/characters/faruzan.json +11 -10
  284. package/data/genshinimpact/characters/fischl.json +11 -10
  285. package/data/genshinimpact/characters/flins.json +34 -0
  286. package/data/genshinimpact/characters/freminet.json +10 -9
  287. package/data/genshinimpact/characters/furina.json +11 -10
  288. package/data/genshinimpact/characters/gaming.json +10 -9
  289. package/data/genshinimpact/characters/ganyu.json +10 -9
  290. package/data/genshinimpact/characters/gorou.json +11 -10
  291. package/data/genshinimpact/characters/hutao.json +11 -10
  292. package/data/genshinimpact/characters/iansan.json +10 -9
  293. package/data/genshinimpact/characters/ifa.json +34 -0
  294. package/data/genshinimpact/characters/illuga.json +34 -0
  295. package/data/genshinimpact/characters/ineffa.json +10 -9
  296. package/data/genshinimpact/characters/jahoda.json +34 -0
  297. package/data/genshinimpact/characters/jean.json +10 -9
  298. package/data/genshinimpact/characters/kachina.json +10 -9
  299. package/data/genshinimpact/characters/kaedeharakazuha.json +11 -10
  300. package/data/genshinimpact/characters/kaeya.json +10 -9
  301. package/data/genshinimpact/characters/kamisatoayaka.json +10 -9
  302. package/data/genshinimpact/characters/kamisatoayato.json +10 -9
  303. package/data/genshinimpact/characters/kaveh.json +11 -10
  304. package/data/genshinimpact/characters/keqing.json +10 -9
  305. package/data/genshinimpact/characters/kinich.json +10 -9
  306. package/data/genshinimpact/characters/kirara.json +10 -9
  307. package/data/genshinimpact/characters/klee.json +10 -9
  308. package/data/genshinimpact/characters/kujousara.json +10 -9
  309. package/data/genshinimpact/characters/kukishinobu.json +11 -10
  310. package/data/genshinimpact/characters/lanyan.json +10 -9
  311. package/data/genshinimpact/characters/lauma.json +34 -0
  312. package/data/genshinimpact/characters/layla.json +10 -9
  313. package/data/genshinimpact/characters/linnea.json +34 -0
  314. package/data/genshinimpact/characters/lisa.json +11 -10
  315. package/data/genshinimpact/characters/lohen.json +10 -9
  316. package/data/genshinimpact/characters/lynette.json +10 -9
  317. package/data/genshinimpact/characters/lyney.json +10 -9
  318. package/data/genshinimpact/characters/mavuika.json +10 -9
  319. package/data/genshinimpact/characters/mika.json +34 -0
  320. package/data/genshinimpact/characters/mikaschmidt.json +10 -9
  321. package/data/genshinimpact/characters/mona.json +11 -10
  322. package/data/genshinimpact/characters/mualani.json +10 -9
  323. package/data/genshinimpact/characters/nahida.json +11 -10
  324. package/data/genshinimpact/characters/navia.json +10 -9
  325. package/data/genshinimpact/characters/nefer.json +34 -0
  326. package/data/genshinimpact/characters/neuvillette.json +10 -9
  327. package/data/genshinimpact/characters/nicole.json +34 -0
  328. package/data/genshinimpact/characters/nilou.json +10 -9
  329. package/data/genshinimpact/characters/ningguang.json +10 -9
  330. package/data/genshinimpact/characters/noelle.json +11 -10
  331. package/data/genshinimpact/characters/odette.json +34 -0
  332. package/data/genshinimpact/characters/ororon.json +10 -9
  333. package/data/genshinimpact/characters/prune.json +9 -8
  334. package/data/genshinimpact/characters/qiqi.json +11 -10
  335. package/data/genshinimpact/characters/raidenshogun.json +11 -10
  336. package/data/genshinimpact/characters/razor.json +10 -9
  337. package/data/genshinimpact/characters/rosaria.json +11 -10
  338. package/data/genshinimpact/characters/sandrone.json +34 -0
  339. package/data/genshinimpact/characters/sangonomiyakokomi.json +11 -10
  340. package/data/genshinimpact/characters/sayu.json +10 -9
  341. package/data/genshinimpact/characters/sethos.json +10 -9
  342. package/data/genshinimpact/characters/shenhe.json +11 -10
  343. package/data/genshinimpact/characters/shikanoinheizou.json +11 -10
  344. package/data/genshinimpact/characters/sigewinne.json +10 -9
  345. package/data/genshinimpact/characters/skirk.json +10 -9
  346. package/data/genshinimpact/characters/sucrose.json +11 -10
  347. package/data/genshinimpact/characters/tartaglia.json +34 -0
  348. package/data/genshinimpact/characters/thoma.json +10 -9
  349. package/data/genshinimpact/characters/tighnari.json +11 -10
  350. package/data/genshinimpact/characters/traveler.json +34 -0
  351. package/data/genshinimpact/characters/varesa.json +10 -9
  352. package/data/genshinimpact/characters/varka.json +9 -8
  353. package/data/genshinimpact/characters/venti.json +11 -10
  354. package/data/genshinimpact/characters/wanderer.json +10 -9
  355. package/data/genshinimpact/characters/wonderlandmanekin.json +34 -0
  356. package/data/genshinimpact/characters/wriothesley.json +11 -10
  357. package/data/genshinimpact/characters/xiangling.json +11 -10
  358. package/data/genshinimpact/characters/xianyun.json +10 -9
  359. package/data/genshinimpact/characters/xiao.json +10 -9
  360. package/data/genshinimpact/characters/xilonen.json +10 -9
  361. package/data/genshinimpact/characters/xingqiu.json +11 -10
  362. package/data/genshinimpact/characters/xinyan.json +11 -10
  363. package/data/genshinimpact/characters/yaemiko.json +11 -10
  364. package/data/genshinimpact/characters/yanfei.json +11 -10
  365. package/data/genshinimpact/characters/yaoyao.json +11 -10
  366. package/data/genshinimpact/characters/yelan.json +10 -9
  367. package/data/genshinimpact/characters/yoimiya.json +11 -10
  368. package/data/genshinimpact/characters/yumemizukimizuki.json +10 -9
  369. package/data/genshinimpact/characters/yunjin.json +10 -9
  370. package/data/genshinimpact/characters/zhongli.json +10 -9
  371. package/data/genshinimpact/characters/zibai.json +10 -9
  372. package/data/genshinimpact/meta.json +12 -12
  373. package/data/honkaistarrail/characters/acheron.json +11 -10
  374. package/data/honkaistarrail/characters/aglaea.json +11 -10
  375. package/data/honkaistarrail/characters/anaxa.json +11 -10
  376. package/data/honkaistarrail/characters/archer.json +29 -0
  377. package/data/honkaistarrail/characters/argenti.json +11 -10
  378. package/data/honkaistarrail/characters/arlan.json +11 -10
  379. package/data/honkaistarrail/characters/ashveil.json +11 -10
  380. package/data/honkaistarrail/characters/asta.json +11 -10
  381. package/data/honkaistarrail/characters/aventurine.json +11 -10
  382. package/data/honkaistarrail/characters/bailu.json +11 -10
  383. package/data/honkaistarrail/characters/blackswan.json +11 -10
  384. package/data/honkaistarrail/characters/blade.json +11 -10
  385. package/data/honkaistarrail/characters/boothill.json +11 -10
  386. package/data/honkaistarrail/characters/bronya.json +11 -10
  387. package/data/honkaistarrail/characters/castorice.json +11 -10
  388. package/data/honkaistarrail/characters/cerydra.json +11 -10
  389. package/data/honkaistarrail/characters/cipher.json +11 -10
  390. package/data/honkaistarrail/characters/clara.json +11 -10
  391. package/data/honkaistarrail/characters/constance.json +11 -10
  392. package/data/honkaistarrail/characters/cyrene.json +11 -10
  393. package/data/honkaistarrail/characters/danheng.json +11 -10
  394. package/data/honkaistarrail/characters/danhengimbibitorlunae.json +29 -0
  395. package/data/honkaistarrail/characters/danhengpermansorterrae.json +29 -0
  396. package/data/honkaistarrail/characters/drratio.json +11 -10
  397. package/data/honkaistarrail/characters/evanescia.json +29 -0
  398. package/data/honkaistarrail/characters/evernight.json +29 -0
  399. package/data/honkaistarrail/characters/feixiao.json +11 -10
  400. package/data/honkaistarrail/characters/firefly.json +11 -10
  401. package/data/honkaistarrail/characters/fugue.json +29 -0
  402. package/data/honkaistarrail/characters/fuxuan.json +11 -10
  403. package/data/honkaistarrail/characters/gallagher.json +11 -10
  404. package/data/honkaistarrail/characters/gepard.json +11 -10
  405. package/data/honkaistarrail/characters/gilgamesh.json +29 -0
  406. package/data/honkaistarrail/characters/guinaifen.json +11 -10
  407. package/data/honkaistarrail/characters/hanya.json +11 -10
  408. package/data/honkaistarrail/characters/herta.json +11 -10
  409. package/data/honkaistarrail/characters/himeko.json +12 -11
  410. package/data/honkaistarrail/characters/himekonova.json +29 -0
  411. package/data/honkaistarrail/characters/hook.json +11 -10
  412. package/data/honkaistarrail/characters/huohuo.json +11 -10
  413. package/data/honkaistarrail/characters/hyacine.json +11 -10
  414. package/data/honkaistarrail/characters/hysilens.json +11 -10
  415. package/data/honkaistarrail/characters/jade.json +11 -10
  416. package/data/honkaistarrail/characters/jiaoqiu.json +11 -10
  417. package/data/honkaistarrail/characters/jingliu.json +11 -10
  418. package/data/honkaistarrail/characters/jingyuan.json +11 -10
  419. package/data/honkaistarrail/characters/kafka.json +11 -10
  420. package/data/honkaistarrail/characters/lingsha.json +11 -10
  421. package/data/honkaistarrail/characters/lisushang.json +11 -10
  422. package/data/honkaistarrail/characters/luka.json +11 -10
  423. package/data/honkaistarrail/characters/luocha.json +11 -10
  424. package/data/honkaistarrail/characters/lynx.json +11 -10
  425. package/data/honkaistarrail/characters/march7th.json +11 -10
  426. package/data/honkaistarrail/characters/misha.json +11 -10
  427. package/data/honkaistarrail/characters/mortenaxblade.json +29 -0
  428. package/data/honkaistarrail/characters/moze.json +11 -10
  429. package/data/honkaistarrail/characters/mydei.json +11 -10
  430. package/data/honkaistarrail/characters/natasha.json +11 -10
  431. package/data/honkaistarrail/characters/pela.json +11 -10
  432. package/data/honkaistarrail/characters/phainon.json +11 -10
  433. package/data/honkaistarrail/characters/qingque.json +11 -10
  434. package/data/honkaistarrail/characters/rappa.json +11 -10
  435. package/data/honkaistarrail/characters/rintohsaka.json +29 -0
  436. package/data/honkaistarrail/characters/robin.json +11 -10
  437. package/data/honkaistarrail/characters/ruanmei.json +11 -10
  438. package/data/honkaistarrail/characters/saber.json +29 -0
  439. package/data/honkaistarrail/characters/sampo.json +11 -10
  440. package/data/honkaistarrail/characters/seele.json +11 -10
  441. package/data/honkaistarrail/characters/serval.json +11 -10
  442. package/data/honkaistarrail/characters/silverwolf.json +11 -10
  443. package/data/honkaistarrail/characters/silverwolflv999.json +29 -0
  444. package/data/honkaistarrail/characters/sparkle.json +11 -10
  445. package/data/honkaistarrail/characters/sparxie.json +29 -0
  446. package/data/honkaistarrail/characters/sunday.json +29 -0
  447. package/data/honkaistarrail/characters/thedahlia.json +29 -0
  448. package/data/honkaistarrail/characters/theherta.json +29 -0
  449. package/data/honkaistarrail/characters/tingyun.json +11 -10
  450. package/data/honkaistarrail/characters/topaz.json +11 -10
  451. package/data/honkaistarrail/characters/topaznumby.json +29 -0
  452. package/data/honkaistarrail/characters/trailblazer.json +29 -0
  453. package/data/honkaistarrail/characters/tribbie.json +11 -10
  454. package/data/honkaistarrail/characters/welt.json +29 -0
  455. package/data/honkaistarrail/characters/weltyang.json +11 -10
  456. package/data/honkaistarrail/characters/xueyi.json +11 -10
  457. package/data/honkaistarrail/characters/yanqing.json +11 -10
  458. package/data/honkaistarrail/characters/yaoguang.json +11 -10
  459. package/data/honkaistarrail/characters/yukong.json +11 -10
  460. package/data/honkaistarrail/characters/yunli.json +11 -10
  461. package/data/honkaistarrail/meta.json +12 -12
  462. package/data/nte/characters/adler.json +13 -12
  463. package/data/nte/characters/aurelia.json +13 -12
  464. package/data/nte/characters/baicang.json +13 -12
  465. package/data/nte/characters/chaos.json +13 -12
  466. package/data/nte/characters/chiz.json +13 -12
  467. package/data/nte/characters/daffodill.json +13 -12
  468. package/data/nte/characters/edgar.json +13 -12
  469. package/data/nte/characters/esperzero.json +13 -12
  470. package/data/nte/characters/fadia.json +13 -12
  471. package/data/nte/characters/haniel.json +13 -12
  472. package/data/nte/characters/hathor.json +13 -12
  473. package/data/nte/characters/hotori.json +13 -12
  474. package/data/nte/characters/iroi.json +13 -12
  475. package/data/nte/characters/jiuyuan.json +13 -12
  476. package/data/nte/characters/lacrimosa.json +13 -12
  477. package/data/nte/characters/mint.json +13 -12
  478. package/data/nte/characters/nanally.json +13 -12
  479. package/data/nte/characters/sakiri.json +13 -12
  480. package/data/nte/characters/shinku.json +13 -12
  481. package/data/nte/characters/skia.json +13 -12
  482. package/data/nte/characters/zankou.json +51 -0
  483. package/data/nte/meta.json +12 -12
  484. package/data/toweroffantasy/characters/alyss.json +27 -0
  485. package/data/toweroffantasy/characters/annabella.json +23 -0
  486. package/data/toweroffantasy/characters/asuka.json +23 -0
  487. package/data/toweroffantasy/characters/bailing.json +23 -0
  488. package/data/toweroffantasy/characters/brevey.json +23 -0
  489. package/data/toweroffantasy/characters/claudia.json +23 -0
  490. package/data/toweroffantasy/characters/cobaltb.json +23 -0
  491. package/data/toweroffantasy/characters/cocoritter.json +23 -0
  492. package/data/toweroffantasy/characters/crow.json +23 -0
  493. package/data/toweroffantasy/characters/echo.json +23 -0
  494. package/data/toweroffantasy/characters/ene.json +23 -0
  495. package/data/toweroffantasy/characters/feise.json +25 -0
  496. package/data/toweroffantasy/characters/fenrir.json +23 -0
  497. package/data/toweroffantasy/characters/fiona.json +25 -0
  498. package/data/toweroffantasy/characters/frigg.json +23 -0
  499. package/data/toweroffantasy/characters/gnonno.json +23 -0
  500. package/data/toweroffantasy/characters/hilda.json +23 -0
  501. package/data/toweroffantasy/characters/huang.json +25 -0
  502. package/data/toweroffantasy/characters/huma.json +25 -0
  503. package/data/toweroffantasy/characters/icarus.json +23 -0
  504. package/data/toweroffantasy/characters/jiyu.json +25 -0
  505. package/data/toweroffantasy/characters/king.json +23 -0
  506. package/data/toweroffantasy/characters/lan.json +25 -0
  507. package/data/toweroffantasy/characters/lin.json +23 -0
  508. package/data/toweroffantasy/characters/linghan.json +23 -0
  509. package/data/toweroffantasy/characters/liuhuo.json +23 -0
  510. package/data/toweroffantasy/characters/lyra.json +23 -0
  511. package/data/toweroffantasy/characters/meryl.json +23 -0
  512. package/data/toweroffantasy/characters/mingjing.json +27 -0
  513. package/data/toweroffantasy/characters/nanyin.json +25 -0
  514. package/data/toweroffantasy/characters/nemesis.json +25 -0
  515. package/data/toweroffantasy/characters/pepper.json +23 -0
  516. package/data/toweroffantasy/characters/plotti.json +25 -0
  517. package/data/toweroffantasy/characters/roslyn.json +23 -0
  518. package/data/toweroffantasy/characters/rubilia.json +25 -0
  519. package/data/toweroffantasy/characters/ruby.json +25 -0
  520. package/data/toweroffantasy/characters/sakifuwa.json +23 -0
  521. package/data/toweroffantasy/characters/samir.json +23 -0
  522. package/data/toweroffantasy/characters/shiro.json +25 -0
  523. package/data/toweroffantasy/characters/tianlang.json +25 -0
  524. package/data/toweroffantasy/characters/tsubasa.json +23 -0
  525. package/data/toweroffantasy/characters/umi.json +23 -0
  526. package/data/toweroffantasy/characters/yanmiao.json +25 -0
  527. package/data/toweroffantasy/characters/yanuo.json +23 -0
  528. package/data/toweroffantasy/characters/yulan.json +23 -0
  529. package/data/toweroffantasy/characters/zero.json +23 -0
  530. package/data/toweroffantasy/meta.json +14 -0
  531. package/data/vrchat/groups/clubzodiac.json +2 -1
  532. package/data/vrchat/groups/dirtydancers.json +2 -1
  533. package/data/vrchat/groups/drumsynews.json +2 -1
  534. package/data/vrchat/groups/ebola20.json +2 -1
  535. package/data/vrchat/groups/internationaldancersassociation.json +2 -1
  536. package/data/vrchat/groups/kannagang.json +2 -1
  537. package/data/vrchat/groups/lolisquad.json +2 -1
  538. package/data/vrchat/groups/nekonightsnightclub.json +2 -1
  539. package/data/vrchat/groups/nekostudios.json +2 -1
  540. package/data/vrchat/groups/nekosunecommunity.json +2 -1
  541. package/data/vrchat/groups/voidclub.json +2 -1
  542. package/data/vrchat/groups/voltdanceclub.json +2 -1
  543. package/data/vrchat/groups/vrceventsgroup.json +2 -1
  544. package/data/vrchat/groups/vrchatcommunitydiscord.json +2 -1
  545. package/data/vrchat/meta.json +11 -11
  546. package/data/vrchat/players/alto.json +8 -7
  547. package/data/vrchat/players/arcadum.json +9 -8
  548. package/data/vrchat/players/chairhandler.json +8 -7
  549. package/data/vrchat/players/chipz.json +8 -7
  550. package/data/vrchat/players/hikarivr.json +10 -9
  551. package/data/vrchat/players/kimple.json +9 -8
  552. package/data/vrchat/players/linkzr.json +7 -6
  553. package/data/vrchat/players/nagzz21.json +8 -7
  554. package/data/vrchat/players/nebride.json +9 -8
  555. package/data/vrchat/players/nekosunevr.json +10 -9
  556. package/data/vrchat/players/pokelawls.json +8 -7
  557. package/data/vrchat/players/qwonk.json +7 -6
  558. package/data/vrchat/players/roflgator.json +9 -8
  559. package/data/vrchat/players/scifri.json +6 -5
  560. package/data/vrchat/players/sodapoppin.json +8 -7
  561. package/data/vrchat/players/woops.json +8 -7
  562. package/data/vrchat/players/zentreya.json +9 -8
  563. package/data/vrchat/worlds/ascensionacademy.json +6 -5
  564. package/data/vrchat/worlds/clubvgl.json +7 -6
  565. package/data/vrchat/worlds/fightfinder.json +7 -6
  566. package/data/vrchat/worlds/hangouthouse.json +6 -5
  567. package/data/vrchat/worlds/nekostudioshalloween.json +8 -7
  568. package/data/vrchat/worlds/nekostudiosnewyear.json +8 -7
  569. package/data/vrchat/worlds/nekostudiosxmas.json +8 -7
  570. package/data/vrchat/worlds/nekosunevranimations2023.json +8 -7
  571. package/data/vrchat/worlds/nekosunevranimations20232024.json +10 -0
  572. package/data/vrchat/worlds/nekosunevranimations2024.json +8 -7
  573. package/data/vrchat/worlds/nekosunevranimations2025.json +8 -7
  574. package/data/vrchat/worlds/rumhamsavatarworld.json +6 -5
  575. package/data/vrchat/worlds/smewsranch.json +6 -5
  576. package/data/vrchat/worlds/smewsroom.json +6 -5
  577. package/data/vrchat/worlds/sunsetbar.json +6 -5
  578. package/data/vrchat/worlds/theblackcat.json +9 -8
  579. package/data/vrchat/worlds/thegreatpug.json +7 -6
  580. package/data/vrchat/worlds/themidnightbar.json +6 -5
  581. package/data/vrchat/worlds/voidclub.json +6 -5
  582. package/data/vrchat/worlds/voltdanceclub.json +6 -5
  583. package/data/vrchat/worlds/vrchathome.json +7 -6
  584. package/data/vrchat/worlds/whatalegend.json +5 -4
  585. package/data/warframe/characters/amesha.json +226 -0
  586. package/data/warframe/characters/ash.json +1354 -0
  587. package/data/warframe/characters/ashprime.json +1994 -0
  588. package/data/warframe/characters/atlas.json +916 -0
  589. package/data/warframe/characters/atlasprime.json +1632 -0
  590. package/data/warframe/characters/banshee.json +1036 -0
  591. package/data/warframe/characters/bansheeprime.json +1750 -0
  592. package/data/warframe/characters/baruuk.json +555 -0
  593. package/data/warframe/characters/baruukprime.json +1298 -0
  594. package/data/warframe/characters/bonewidow.json +354 -0
  595. package/data/warframe/characters/caliban.json +434 -0
  596. package/data/warframe/characters/calibanprime.json +683 -0
  597. package/data/warframe/characters/chroma.json +1344 -0
  598. package/data/warframe/characters/chromaprime.json +1977 -0
  599. package/data/warframe/characters/citrine.json +220 -0
  600. package/data/warframe/characters/cyte09.json +218 -0
  601. package/data/warframe/characters/dagath.json +194 -0
  602. package/data/warframe/characters/dante.json +223 -0
  603. package/data/warframe/characters/elytron.json +300 -0
  604. package/data/warframe/characters/ember.json +1209 -0
  605. package/data/warframe/characters/emberprime.json +1678 -0
  606. package/data/warframe/characters/equinox.json +958 -0
  607. package/data/warframe/characters/equinoxprime.json +1644 -0
  608. package/data/warframe/characters/excalibur.json +1603 -0
  609. package/data/warframe/characters/excaliburprime.json +1524 -0
  610. package/data/warframe/characters/excaliburumbra.json +397 -0
  611. package/data/warframe/characters/follie.json +246 -0
  612. package/data/warframe/characters/frost.json +1330 -0
  613. package/data/warframe/characters/frostprime.json +1686 -0
  614. package/data/warframe/characters/gara.json +925 -0
  615. package/data/warframe/characters/garaprime.json +1494 -0
  616. package/data/warframe/characters/garuda.json +851 -0
  617. package/data/warframe/characters/garudaprime.json +1449 -0
  618. package/data/warframe/characters/gauss.json +506 -0
  619. package/data/warframe/characters/gaussprime.json +1077 -0
  620. package/data/warframe/characters/grendel.json +463 -0
  621. package/data/warframe/characters/grendelprime.json +772 -0
  622. package/data/warframe/characters/gyre.json +381 -0
  623. package/data/warframe/characters/gyreprime.json +403 -0
  624. package/data/warframe/characters/harrow.json +916 -0
  625. package/data/warframe/characters/harrowprime.json +1700 -0
  626. package/data/warframe/characters/helminth.json +220 -0
  627. package/data/warframe/characters/hildryn.json +627 -0
  628. package/data/warframe/characters/hildrynprime.json +996 -0
  629. package/data/warframe/characters/hydroid.json +1028 -0
  630. package/data/warframe/characters/hydroidprime.json +1621 -0
  631. package/data/warframe/characters/inaros.json +976 -0
  632. package/data/warframe/characters/inarosprime.json +1721 -0
  633. package/data/warframe/characters/itzal.json +465 -0
  634. package/data/warframe/characters/ivara.json +1349 -0
  635. package/data/warframe/characters/ivaraprime.json +1921 -0
  636. package/data/warframe/characters/jade.json +240 -0
  637. package/data/warframe/characters/khora.json +804 -0
  638. package/data/warframe/characters/khoraprime.json +1403 -0
  639. package/data/warframe/characters/koumei.json +221 -0
  640. package/data/warframe/characters/kullervo.json +191 -0
  641. package/data/warframe/characters/lavos.json +511 -0
  642. package/data/warframe/characters/lavosprime.json +711 -0
  643. package/data/warframe/characters/limbo.json +1363 -0
  644. package/data/warframe/characters/limboprime.json +2084 -0
  645. package/data/warframe/characters/loki.json +1209 -0
  646. package/data/warframe/characters/lokiprime.json +1457 -0
  647. package/data/warframe/characters/mag.json +2089 -0
  648. package/data/warframe/characters/magprime.json +2532 -0
  649. package/data/warframe/characters/mesa.json +1195 -0
  650. package/data/warframe/characters/mesaprime.json +2059 -0
  651. package/data/warframe/characters/mirage.json +1561 -0
  652. package/data/warframe/characters/mirageprime.json +2257 -0
  653. package/data/warframe/characters/nekros.json +1235 -0
  654. package/data/warframe/characters/nekrosprime.json +1882 -0
  655. package/data/warframe/characters/nezha.json +868 -0
  656. package/data/warframe/characters/nezhaprime.json +1695 -0
  657. package/data/warframe/characters/nidus.json +1027 -0
  658. package/data/warframe/characters/nidusprime.json +1706 -0
  659. package/data/warframe/characters/nokko.json +308 -0
  660. package/data/warframe/characters/nova.json +1226 -0
  661. package/data/warframe/characters/novaprime.json +1667 -0
  662. package/data/warframe/characters/nyx.json +1610 -0
  663. package/data/warframe/characters/nyxprime.json +1967 -0
  664. package/data/warframe/characters/oberon.json +1249 -0
  665. package/data/warframe/characters/oberonprime.json +1872 -0
  666. package/data/warframe/characters/octavia.json +1049 -0
  667. package/data/warframe/characters/octaviaprime.json +1735 -0
  668. package/data/warframe/characters/odonata.json +336 -0
  669. package/data/warframe/characters/odonataprime.json +787 -0
  670. package/data/warframe/characters/oraxia.json +246 -0
  671. package/data/warframe/characters/orionsirius.json +134 -0
  672. package/data/warframe/characters/protea.json +521 -0
  673. package/data/warframe/characters/proteaprime.json +992 -0
  674. package/data/warframe/characters/qorvex.json +214 -0
  675. package/data/warframe/characters/revenant.json +931 -0
  676. package/data/warframe/characters/revenantprime.json +1494 -0
  677. package/data/warframe/characters/rhino.json +1344 -0
  678. package/data/warframe/characters/rhinoprime.json +1675 -0
  679. package/data/warframe/characters/saryn.json +1210 -0
  680. package/data/warframe/characters/sarynprime.json +2042 -0
  681. package/data/warframe/characters/sevagoth.json +478 -0
  682. package/data/warframe/characters/sevagothprime.json +910 -0
  683. package/data/warframe/characters/siriusorion.json +247 -0
  684. package/data/warframe/characters/styanax.json +320 -0
  685. package/data/warframe/characters/styanaxprime.json +321 -0
  686. package/data/warframe/characters/temple.json +271 -0
  687. package/data/warframe/characters/titania.json +1227 -0
  688. package/data/warframe/characters/titaniaprime.json +2000 -0
  689. package/data/warframe/characters/trinity.json +1097 -0
  690. package/data/warframe/characters/trinityprime.json +1849 -0
  691. package/data/warframe/characters/uriel.json +215 -0
  692. package/data/warframe/characters/valkyr.json +1165 -0
  693. package/data/warframe/characters/valkyrprime.json +1969 -0
  694. package/data/warframe/characters/vauban.json +1067 -0
  695. package/data/warframe/characters/vaubanprime.json +1614 -0
  696. package/data/warframe/characters/voidrig.json +355 -0
  697. package/data/warframe/characters/volt.json +1324 -0
  698. package/data/warframe/characters/voltprime.json +1843 -0
  699. package/data/warframe/characters/voruna.json +293 -0
  700. package/data/warframe/characters/vorunaprime.json +431 -0
  701. package/data/warframe/characters/wisp.json +824 -0
  702. package/data/warframe/characters/wispprime.json +908 -0
  703. package/data/warframe/characters/wukong.json +927 -0
  704. package/data/warframe/characters/wukongprime.json +1754 -0
  705. package/data/warframe/characters/xaku.json +620 -0
  706. package/data/warframe/characters/xakuprime.json +879 -0
  707. package/data/warframe/characters/yareli.json +409 -0
  708. package/data/warframe/characters/yareliprime.json +683 -0
  709. package/data/warframe/characters/zephyr.json +1149 -0
  710. package/data/warframe/characters/zephyrprime.json +1864 -0
  711. package/data/warframe/meta.json +14 -0
  712. package/data/wutheringwaves/meta.json +14 -0
  713. package/data/zenlesszonezero/meta.json +14 -0
  714. package/gamecodes.js +204 -0
  715. package/index.js +36 -17
  716. package/package.json +33 -3
  717. package/scripts/cache-sync-smoke.js +139 -0
  718. package/scripts/game-code-cache-smoke.js +125 -0
  719. package/scripts/sync-all-cache.js +18 -0
  720. package/scripts/sync-api-cache.js +368 -0
  721. package/scripts/sync-game-code-cache.js +283 -0
  722. package/scripts/sync-gaming-info-cache.js +46 -0
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ const { syncGamingInfoCacheSafely } = require('./sync-gaming-info-cache');
4
+ const { syncGameCodeCache } = require('./sync-game-code-cache');
5
+
6
+ async function main() {
7
+ // The gaming-info phase temporarily moves data/game-codes out of the way,
8
+ // mirrors data/, then restores the previous code cache before code syncing.
9
+ // If the code API fails afterwards, its last-good fallback is still present.
10
+ const gamingInfos = await syncGamingInfoCacheSafely();
11
+ const gameCodes = await syncGameCodeCache();
12
+ console.log(JSON.stringify({ success: true, gamingInfos, gameCodes }, null, 2));
13
+ }
14
+
15
+ main().catch((error) => {
16
+ console.error(`[gaming-infos combined cache] ${error.stack || error.message || error}`);
17
+ process.exitCode = 1;
18
+ });
@@ -0,0 +1,368 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('crypto');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ const ROOT_DIR = path.resolve(__dirname, '..');
8
+ const DEFAULT_DATA_DIR = path.join(ROOT_DIR, 'data');
9
+ const DEFAULT_API_BASE = 'https://api.nekosunevr.co.uk';
10
+ const DEFAULT_API_PATH = '/v5/games/api/gaming-infos';
11
+
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.
16
+ const DEFAULT_MANIFEST = Object.freeze({
17
+ vrchat: ['players', 'groups', 'worlds', 'avatars'],
18
+ genshinimpact: ['characters'],
19
+ honkaistarrail: ['characters'],
20
+ nte: ['characters'],
21
+ wutheringwaves: ['characters'],
22
+ warframe: ['characters'],
23
+ fortnite: ['characters', 'npcs', 'maps'],
24
+ zenlesszonezero: ['characters'],
25
+ toweroffantasy: ['characters'],
26
+ arknightsendfield: ['characters']
27
+ });
28
+
29
+ function numberOption(value, fallback, min = 0, max = Number.MAX_SAFE_INTEGER) {
30
+ const parsed = Number(value);
31
+ return Number.isFinite(parsed) ? Math.min(max, Math.max(min, parsed)) : fallback;
32
+ }
33
+
34
+ function sleep(ms) {
35
+ return new Promise((resolve) => setTimeout(resolve, ms));
36
+ }
37
+
38
+ function normalizeBase(value) {
39
+ return String(value || DEFAULT_API_BASE).replace(/\/+$/, '');
40
+ }
41
+
42
+ function normalizeApiPath(value) {
43
+ const raw = String(value || DEFAULT_API_PATH).trim();
44
+ return `/${raw.replace(/^\/+|\/+$/g, '')}`;
45
+ }
46
+
47
+ function safeSlug(value, label = 'slug') {
48
+ const slug = String(value || '').trim().toLowerCase();
49
+ if (!slug || !/^[a-z0-9][a-z0-9._-]*$/.test(slug)) {
50
+ throw new Error(`Invalid ${label}: ${JSON.stringify(value)}`);
51
+ }
52
+ return slug;
53
+ }
54
+
55
+ function fallbackSlug(name) {
56
+ const slug = String(name || '')
57
+ .normalize('NFKD')
58
+ .replace(/[\u0300-\u036f]/g, '')
59
+ .toLowerCase()
60
+ .replace(/[^a-z0-9]+/g, '')
61
+ .trim();
62
+ return safeSlug(slug, 'derived slug');
63
+ }
64
+
65
+ function normalizeManifestObject(parsed) {
66
+ if (!parsed || Array.isArray(parsed) || typeof parsed !== 'object') {
67
+ throw new Error('Gaming Infos cache manifest must be a JSON object');
68
+ }
69
+ const result = {};
70
+ for (const [gameRaw, typesRaw] of Object.entries(parsed)) {
71
+ const game = safeSlug(gameRaw, 'game');
72
+ if (!Array.isArray(typesRaw) || !typesRaw.length) throw new Error(`No types configured for ${game}`);
73
+ result[game] = [...new Set(typesRaw.map((type) => safeSlug(type, 'type')))];
74
+ }
75
+ return result;
76
+ }
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
+
95
+ async function requestJson(url, options = {}) {
96
+ const timeoutMs = numberOption(options.timeoutMs ?? process.env.GAMING_INFOS_CACHE_TIMEOUT_MS, 15000, 1000, 120000);
97
+ const retries = numberOption(options.retries ?? process.env.GAMING_INFOS_CACHE_RETRIES, 2, 0, 5);
98
+ const retryDelayMs = numberOption(options.retryDelayMs ?? process.env.GAMING_INFOS_CACHE_RETRY_DELAY_MS, 750, 0, 30000);
99
+ let lastError;
100
+
101
+ for (let attempt = 0; attempt <= retries; attempt += 1) {
102
+ const controller = new AbortController();
103
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
104
+ try {
105
+ const response = await fetch(url, {
106
+ signal: controller.signal,
107
+ headers: {
108
+ Accept: 'application/json',
109
+ 'User-Agent': process.env.GAMING_INFOS_CACHE_USER_AGENT || 'NekoSuneProjects-gaming-infos-cache/1.0'
110
+ }
111
+ });
112
+
113
+ if (!response.ok) {
114
+ const error = new Error(`HTTP ${response.status} from ${url}`);
115
+ error.status = response.status;
116
+ throw error;
117
+ }
118
+
119
+ const body = await response.json();
120
+ if (!body || body.success === false) {
121
+ throw new Error(`API returned an unsuccessful response for ${url}`);
122
+ }
123
+ if (options.rawEnvelope) return body;
124
+ return body.data !== undefined ? body.data : body;
125
+ } catch (error) {
126
+ lastError = error;
127
+ const status = Number(error.status || 0);
128
+ const retryable = !status || status === 408 || status === 425 || status === 429 || status >= 500;
129
+ if (attempt >= retries || !retryable) break;
130
+ await sleep(retryDelayMs * (attempt + 1));
131
+ } finally {
132
+ clearTimeout(timer);
133
+ }
134
+ }
135
+
136
+ throw lastError || new Error(`Failed to fetch ${url}`);
137
+ }
138
+
139
+ function apiUrl(base, apiPath, suffix = '') {
140
+ return `${normalizeBase(base)}${normalizeApiPath(apiPath)}${suffix}`;
141
+ }
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
+
163
+ async function writeJson(filePath, value) {
164
+ await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
165
+ await fs.promises.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
166
+ }
167
+
168
+ async function walkFiles(dir, root = dir, out = new Map()) {
169
+ if (!fs.existsSync(dir)) return out;
170
+ const entries = await fs.promises.readdir(dir, { withFileTypes: true });
171
+ for (const entry of entries) {
172
+ const full = path.join(dir, entry.name);
173
+ if (entry.isDirectory()) await walkFiles(full, root, out);
174
+ else if (entry.isFile() && entry.name !== '.cache-manifest.json') {
175
+ const relative = path.relative(root, full).split(path.sep).join('/');
176
+ out.set(relative, await fs.promises.readFile(full));
177
+ }
178
+ }
179
+ return out;
180
+ }
181
+
182
+ function hashFiles(files) {
183
+ const hash = crypto.createHash('sha256');
184
+ for (const [name, content] of [...files.entries()].sort(([a], [b]) => a.localeCompare(b))) {
185
+ hash.update(name);
186
+ hash.update('\0');
187
+ hash.update(content);
188
+ hash.update('\0');
189
+ }
190
+ return hash.digest('hex');
191
+ }
192
+
193
+ async function snapshotHash(dir) {
194
+ return hashFiles(await walkFiles(dir));
195
+ }
196
+
197
+ function diffStats(oldFiles, newFiles) {
198
+ let added = 0;
199
+ let removed = 0;
200
+ let changed = 0;
201
+ let unchanged = 0;
202
+
203
+ for (const [name, content] of newFiles) {
204
+ if (!oldFiles.has(name)) added += 1;
205
+ else if (!oldFiles.get(name).equals(content)) changed += 1;
206
+ else unchanged += 1;
207
+ }
208
+ for (const name of oldFiles.keys()) if (!newFiles.has(name)) removed += 1;
209
+ return { added, changed, removed, unchanged, total: newFiles.size };
210
+ }
211
+
212
+ async function replaceSnapshot(nextDir, dataDir) {
213
+ const backupDir = `${dataDir}.cache-backup-${process.pid}`;
214
+ await fs.promises.rm(backupDir, { recursive: true, force: true });
215
+ const hadCurrent = fs.existsSync(dataDir);
216
+
217
+ if (hadCurrent) await fs.promises.rename(dataDir, backupDir);
218
+ try {
219
+ await fs.promises.rename(nextDir, dataDir);
220
+ await fs.promises.rm(backupDir, { recursive: true, force: true });
221
+ } catch (error) {
222
+ if (!fs.existsSync(dataDir) && hadCurrent && fs.existsSync(backupDir)) {
223
+ await fs.promises.rename(backupDir, dataDir);
224
+ }
225
+ throw error;
226
+ }
227
+ }
228
+
229
+ function normalizedEntry(item) {
230
+ if (!item || Array.isArray(item) || typeof item !== 'object') throw new Error('API list contained a non-object entry');
231
+ const slug = safeSlug(item.slug || fallbackSlug(item.name), 'entry slug');
232
+ return { ...item, slug };
233
+ }
234
+
235
+ async function buildNextSnapshot(options) {
236
+ const { apiBase, apiPath, dataDir, manifest: configuredManifest } = options;
237
+ const parentDir = path.dirname(dataDir);
238
+ const nextDir = path.join(parentDir, `.gaming-infos-cache-next-${process.pid}-${Date.now()}`);
239
+ await fs.promises.rm(nextDir, { recursive: true, force: true });
240
+ await fs.promises.mkdir(nextDir, { recursive: true });
241
+
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
+ }
270
+ }
271
+ }
272
+
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
+ }
278
+ }
279
+
280
+ async function syncApiCache(options = {}) {
281
+ if (typeof fetch !== 'function') throw new Error('Node.js 18+ is required because this sync uses the built-in fetch API');
282
+
283
+ const dataDir = path.resolve(options.dataDir || process.env.GAMING_INFOS_CACHE_DIR || DEFAULT_DATA_DIR);
284
+ const apiBase = options.apiBase || process.env.GAMING_INFOS_API_BASE || DEFAULT_API_BASE;
285
+ const apiPath = options.apiPath || process.env.GAMING_INFOS_API_PATH || DEFAULT_API_PATH;
286
+ const manifest = options.manifest || loadManifest();
287
+ const requestOptions = {
288
+ ...options,
289
+ apiBase,
290
+ apiPath,
291
+ dataDir,
292
+ manifest
293
+ };
294
+
295
+ let nextDir;
296
+ try {
297
+ const built = await buildNextSnapshot(requestOptions);
298
+ nextDir = built.nextDir;
299
+ const effectiveManifest = built.manifest;
300
+ const oldFiles = await walkFiles(dataDir);
301
+ const newFiles = await walkFiles(nextDir);
302
+ const oldHash = hashFiles(oldFiles);
303
+ const newHash = hashFiles(newFiles);
304
+ const stats = diffStats(oldFiles, newFiles);
305
+
306
+ if (oldFiles.size && oldHash === newHash) {
307
+ await fs.promises.rm(nextDir, { recursive: true, force: true });
308
+ return {
309
+ success: true,
310
+ changed: false,
311
+ contentHash: newHash,
312
+ source: apiUrl(apiBase, apiPath),
313
+ games: Object.keys(effectiveManifest),
314
+ counts: built.counts,
315
+ stats
316
+ };
317
+ }
318
+
319
+ const cacheManifest = {
320
+ schemaVersion: 2,
321
+ source: apiUrl(apiBase, apiPath),
322
+ syncedAt: new Date().toISOString(),
323
+ contentHash: newHash,
324
+ rootGameCount: built.rootGameCount,
325
+ autoDiscoveredTypes: true,
326
+ games: Object.fromEntries(Object.entries(effectiveManifest).map(([game, types]) => [game, { types, counts: built.counts[game] }])),
327
+ stats
328
+ };
329
+ await writeJson(path.join(nextDir, '.cache-manifest.json'), cacheManifest);
330
+ await replaceSnapshot(nextDir, dataDir);
331
+ nextDir = null;
332
+
333
+ return {
334
+ success: true,
335
+ changed: true,
336
+ contentHash: newHash,
337
+ source: cacheManifest.source,
338
+ games: Object.keys(effectiveManifest),
339
+ counts: built.counts,
340
+ stats
341
+ };
342
+ } catch (error) {
343
+ if (nextDir) await fs.promises.rm(nextDir, { recursive: true, force: true }).catch(() => {});
344
+ throw error;
345
+ }
346
+ }
347
+
348
+ async function main() {
349
+ const result = await syncApiCache();
350
+ console.log(JSON.stringify(result, null, 2));
351
+ }
352
+
353
+ if (require.main === module) {
354
+ main().catch((error) => {
355
+ console.error(`[gaming-infos cache] ${error.stack || error.message || error}`);
356
+ process.exitCode = 1;
357
+ });
358
+ }
359
+
360
+ module.exports = {
361
+ DEFAULT_MANIFEST,
362
+ loadManifest,
363
+ mergeDiscoveredManifest,
364
+ validateRootSyncState,
365
+ normalizedEntry,
366
+ snapshotHash,
367
+ syncApiCache
368
+ };
@@ -0,0 +1,283 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('crypto');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ const ROOT_DIR = path.resolve(__dirname, '..');
8
+ const DEFAULT_DATA_DIR = path.join(ROOT_DIR, 'data', 'game-codes');
9
+ const DEFAULT_API_BASE = 'https://api.nekosunevr.co.uk';
10
+ const DEFAULT_API_PATH = '/v5/games/api/codes';
11
+
12
+ function numberOption(value, fallback, min = 0, max = Number.MAX_SAFE_INTEGER) {
13
+ const parsed = Number(value);
14
+ return Number.isFinite(parsed) ? Math.min(max, Math.max(min, parsed)) : fallback;
15
+ }
16
+
17
+ function sleep(ms) {
18
+ return new Promise((resolve) => setTimeout(resolve, ms));
19
+ }
20
+
21
+ function normalizeBase(value) {
22
+ return String(value || DEFAULT_API_BASE).replace(/\/+$/, '');
23
+ }
24
+
25
+ function normalizeApiPath(value) {
26
+ const raw = String(value || DEFAULT_API_PATH).trim();
27
+ return `/${raw.replace(/^\/+|\/+$/g, '')}`;
28
+ }
29
+
30
+ async function requestJson(url, options = {}) {
31
+ const timeoutMs = numberOption(options.timeoutMs ?? process.env.GAME_CODES_CACHE_TIMEOUT_MS, 15000, 1000, 120000);
32
+ const retries = numberOption(options.retries ?? process.env.GAME_CODES_CACHE_RETRIES, 2, 0, 5);
33
+ const retryDelayMs = numberOption(options.retryDelayMs ?? process.env.GAME_CODES_CACHE_RETRY_DELAY_MS, 750, 0, 30000);
34
+ let lastError;
35
+
36
+ for (let attempt = 0; attempt <= retries; attempt += 1) {
37
+ const controller = new AbortController();
38
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
39
+ try {
40
+ const response = await fetch(url, {
41
+ signal: controller.signal,
42
+ headers: {
43
+ Accept: 'application/json',
44
+ 'User-Agent': process.env.GAME_CODES_CACHE_USER_AGENT || 'NekoSuneProjects-gaming-infos-game-codes-cache/1.0'
45
+ }
46
+ });
47
+ if (!response.ok) {
48
+ const error = new Error(`HTTP ${response.status} from ${url}`);
49
+ error.status = response.status;
50
+ throw error;
51
+ }
52
+ const body = await response.json();
53
+ if (!body || body.success === false) throw new Error(`API returned an unsuccessful response for ${url}`);
54
+ return body;
55
+ } catch (error) {
56
+ lastError = error;
57
+ const status = Number(error.status || 0);
58
+ const retryable = !status || status === 408 || status === 425 || status === 429 || status >= 500;
59
+ if (attempt >= retries || !retryable) break;
60
+ await sleep(retryDelayMs * (attempt + 1));
61
+ } finally {
62
+ clearTimeout(timer);
63
+ }
64
+ }
65
+
66
+ throw lastError || new Error(`Failed to fetch ${url}`);
67
+ }
68
+
69
+ function apiUrl(base, apiPath, suffix = '', query = {}) {
70
+ const url = new URL(`${normalizeBase(base)}${normalizeApiPath(apiPath)}${suffix}`);
71
+ for (const [key, value] of Object.entries(query)) {
72
+ if (value !== undefined && value !== null && value !== '') url.searchParams.set(key, String(value));
73
+ }
74
+ return url.href;
75
+ }
76
+
77
+ async function writeJson(filePath, value) {
78
+ await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
79
+ await fs.promises.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
80
+ }
81
+
82
+ async function walkFiles(dir, root = dir, out = new Map()) {
83
+ if (!fs.existsSync(dir)) return out;
84
+ const entries = await fs.promises.readdir(dir, { withFileTypes: true });
85
+ for (const entry of entries) {
86
+ const full = path.join(dir, entry.name);
87
+ if (entry.isDirectory()) await walkFiles(full, root, out);
88
+ else if (entry.isFile() && entry.name !== '.cache-manifest.json') {
89
+ const relative = path.relative(root, full).split(path.sep).join('/');
90
+ out.set(relative, await fs.promises.readFile(full));
91
+ }
92
+ }
93
+ return out;
94
+ }
95
+
96
+ function hashFiles(files) {
97
+ const hash = crypto.createHash('sha256');
98
+ for (const [name, content] of [...files.entries()].sort(([a], [b]) => a.localeCompare(b))) {
99
+ hash.update(name);
100
+ hash.update('\0');
101
+ hash.update(content);
102
+ hash.update('\0');
103
+ }
104
+ return hash.digest('hex');
105
+ }
106
+
107
+ function diffStats(oldFiles, newFiles) {
108
+ let added = 0;
109
+ let removed = 0;
110
+ let changed = 0;
111
+ let unchanged = 0;
112
+ for (const [name, content] of newFiles) {
113
+ if (!oldFiles.has(name)) added += 1;
114
+ else if (!oldFiles.get(name).equals(content)) changed += 1;
115
+ else unchanged += 1;
116
+ }
117
+ for (const name of oldFiles.keys()) if (!newFiles.has(name)) removed += 1;
118
+ return { added, changed, removed, unchanged, total: newFiles.size };
119
+ }
120
+
121
+ async function replaceSnapshot(nextDir, dataDir) {
122
+ const backupDir = `${dataDir}.cache-backup-${process.pid}`;
123
+ await fs.promises.rm(backupDir, { recursive: true, force: true });
124
+ const hadCurrent = fs.existsSync(dataDir);
125
+ if (hadCurrent) await fs.promises.rename(dataDir, backupDir);
126
+ try {
127
+ await fs.promises.rename(nextDir, dataDir);
128
+ await fs.promises.rm(backupDir, { recursive: true, force: true });
129
+ } catch (error) {
130
+ if (!fs.existsSync(dataDir) && hadCurrent && fs.existsSync(backupDir)) await fs.promises.rename(backupDir, dataDir);
131
+ throw error;
132
+ }
133
+ }
134
+
135
+ function gameSlug(row) {
136
+ const slug = String(row?.game || '').trim().toLowerCase();
137
+ if (!slug || !/^[a-z0-9][a-z0-9-]*$/.test(slug)) throw new Error(`Invalid game slug in code directory: ${JSON.stringify(row?.game)}`);
138
+ return slug;
139
+ }
140
+
141
+ function validateCodeList(game, payload) {
142
+ if (!payload || Array.isArray(payload) || typeof payload !== 'object') throw new Error(`Invalid code payload for ${game}`);
143
+ if (!Array.isArray(payload.Active) || !Array.isArray(payload.Expired)) throw new Error(`Code payload for ${game} is missing Active/Expired arrays`);
144
+ if (payload.Unknown !== undefined && !Array.isArray(payload.Unknown)) throw new Error(`Unknown codes for ${game} must be an array`);
145
+ return payload;
146
+ }
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
+
182
+ async function buildNextSnapshot(options) {
183
+ const { apiBase, apiPath, dataDir } = options;
184
+ const nextDir = path.join(path.dirname(dataDir), `.game-codes-cache-next-${process.pid}-${Date.now()}`);
185
+ await fs.promises.rm(nextDir, { recursive: true, force: true });
186
+ await fs.promises.mkdir(nextDir, { recursive: true });
187
+
188
+ const rawDirectory = await requestJson(apiUrl(apiBase, apiPath), options);
189
+ if (!Array.isArray(rawDirectory.games) || !rawDirectory.games.length) {
190
+ throw new Error('Game code directory returned no games; refusing to replace fallback cache');
191
+ }
192
+ const directory = stableDirectorySnapshot(rawDirectory);
193
+ await writeJson(path.join(nextDir, 'directory.json'), directory);
194
+
195
+ // /status is cache-only on APINODE and does not trigger source website requests.
196
+ const statusResponse = await requestJson(apiUrl(apiBase, apiPath, '/status'), options);
197
+ const status = statusResponse.data !== undefined ? statusResponse.data : statusResponse;
198
+ if (!status || typeof status !== 'object') throw new Error('Invalid game code status payload');
199
+ await writeJson(path.join(nextDir, 'status.json'), status);
200
+
201
+ const counts = {};
202
+ const delayMs = numberOption(options.delayMs ?? process.env.GAME_CODES_CACHE_GAME_DELAY_MS, 150, 0, 10000);
203
+ for (let i = 0; i < directory.games.length; i += 1) {
204
+ const game = gameSlug(directory.games[i]);
205
+ const rawPayload = validateCodeList(
206
+ game,
207
+ await requestJson(apiUrl(apiBase, apiPath, `/${encodeURIComponent(game)}`, { includeUnknown: 'true' }), options)
208
+ );
209
+ const payload = stableCodePayload(rawPayload);
210
+ await writeJson(path.join(nextDir, 'games', `${game}.json`), payload);
211
+ counts[game] = {
212
+ active: payload.Active.length,
213
+ expired: payload.Expired.length,
214
+ unknown: Array.isArray(payload.Unknown) ? payload.Unknown.length : 0,
215
+ total: payload.Active.length + payload.Expired.length + (Array.isArray(payload.Unknown) ? payload.Unknown.length : 0)
216
+ };
217
+ if (i < directory.games.length - 1 && delayMs) await sleep(delayMs);
218
+ }
219
+
220
+ return { nextDir, directory, counts };
221
+ }
222
+
223
+ async function syncGameCodeCache(options = {}) {
224
+ if (typeof fetch !== 'function') throw new Error('Node.js 18+ is required because this sync uses the built-in fetch API');
225
+
226
+ const dataDir = path.resolve(options.dataDir || process.env.GAME_CODES_CACHE_DIR || DEFAULT_DATA_DIR);
227
+ const apiBase = options.apiBase || process.env.GAMING_INFOS_API_BASE || DEFAULT_API_BASE;
228
+ const apiPath = options.apiPath || process.env.GAME_CODES_API_PATH || DEFAULT_API_PATH;
229
+ let nextDir;
230
+
231
+ try {
232
+ const built = await buildNextSnapshot({ ...options, dataDir, apiBase, apiPath });
233
+ nextDir = built.nextDir;
234
+ const oldFiles = await walkFiles(dataDir);
235
+ const newFiles = await walkFiles(nextDir);
236
+ const oldHash = hashFiles(oldFiles);
237
+ const newHash = hashFiles(newFiles);
238
+ const stats = diffStats(oldFiles, newFiles);
239
+
240
+ if (oldFiles.size && oldHash === newHash) {
241
+ await fs.promises.rm(nextDir, { recursive: true, force: true });
242
+ return { success: true, changed: false, contentHash: newHash, source: apiUrl(apiBase, apiPath), counts: built.counts, stats };
243
+ }
244
+
245
+ const manifest = {
246
+ schemaVersion: 1,
247
+ source: apiUrl(apiBase, apiPath),
248
+ syncedAt: new Date().toISOString(),
249
+ contentHash: newHash,
250
+ gameCount: built.directory.games.length,
251
+ counts: built.counts,
252
+ statusIsCacheOnly: true,
253
+ volatileFieldsRemoved: ['directory.updatedAt(request-time)', 'expiry.expiresInSeconds', 'expiry.countdown'],
254
+ stats
255
+ };
256
+ await writeJson(path.join(nextDir, '.cache-manifest.json'), manifest);
257
+ await replaceSnapshot(nextDir, dataDir);
258
+ nextDir = null;
259
+ return { success: true, changed: true, contentHash: newHash, source: manifest.source, counts: built.counts, stats };
260
+ } catch (error) {
261
+ if (nextDir) await fs.promises.rm(nextDir, { recursive: true, force: true }).catch(() => {});
262
+ throw error;
263
+ }
264
+ }
265
+
266
+ async function main() {
267
+ const result = await syncGameCodeCache();
268
+ console.log(JSON.stringify(result, null, 2));
269
+ }
270
+
271
+ if (require.main === module) {
272
+ main().catch((error) => {
273
+ console.error(`[game-codes cache] ${error.stack || error.message || error}`);
274
+ process.exitCode = 1;
275
+ });
276
+ }
277
+
278
+ module.exports = {
279
+ stableDirectorySnapshot,
280
+ stableCodePayload,
281
+ syncGameCodeCache,
282
+ validateCodeList
283
+ };