Compare commits
9
Commits
ac98499471
...
9ba8dc26a0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ba8dc26a0 | ||
|
|
af8aaaa512 | ||
|
|
00b5a6f5c3 | ||
|
|
02ad9c21f2 | ||
|
|
101c73df1b | ||
|
|
d1dafba2c9 | ||
|
|
936053cca6 | ||
|
|
79b82e0a31 | ||
|
|
4d4cbd2211 |
No files matched your search
+2
-2
@@ -41,7 +41,7 @@
|
||||
"jpeg-js": "0.4.4",
|
||||
"json5": "2.2.3",
|
||||
"jszip": "3.10.1",
|
||||
"lenis": "1.3.25",
|
||||
"lenis": "1.3.26",
|
||||
"lucide-react": "1.28.0",
|
||||
"lzma": "2.3.2",
|
||||
"motion": "12.43.0",
|
||||
@@ -82,7 +82,7 @@
|
||||
"pino-pretty": "^13.1.3",
|
||||
"postcss": "^8.5.25",
|
||||
"tailwindcss": "^4.3.3",
|
||||
"tsx": "^4.23.5",
|
||||
"tsx": "^4.23.8",
|
||||
"typescript": "^7.0.2",
|
||||
"vite": "8.2.0",
|
||||
"vitest": "4.1.10"
|
||||
|
||||
Generated
+28
-17
@@ -74,8 +74,8 @@ importers:
|
||||
specifier: 3.10.1
|
||||
version: 3.10.1
|
||||
lenis:
|
||||
specifier: 1.3.25
|
||||
version: 1.3.25([email protected])
|
||||
specifier: 1.3.26
|
||||
version: 1.3.26([email protected])
|
||||
lucide-react:
|
||||
specifier: 1.28.0
|
||||
version: 1.28.0([email protected])
|
||||
@@ -192,17 +192,17 @@ importers:
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3
|
||||
tsx:
|
||||
specifier: ^4.23.5
|
||||
version: 4.23.5
|
||||
specifier: ^4.23.8
|
||||
version: 4.23.8
|
||||
typescript:
|
||||
specifier: ^7.0.2
|
||||
version: 7.0.2
|
||||
vite:
|
||||
specifier: 8.2.0
|
||||
version: 8.2.0(@types/[email protected])([email protected])([email protected])([email protected].5)([email protected])
|
||||
version: 8.2.0(@types/[email protected])([email protected])([email protected])([email protected].8)([email protected])
|
||||
vitest:
|
||||
specifier: 4.1.10
|
||||
version: 4.1.10(@types/[email protected])(@vitest/[email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected].5)([email protected]))
|
||||
version: 4.1.10(@types/[email protected])(@vitest/[email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected].8)([email protected]))
|
||||
|
||||
packages:
|
||||
|
||||
@@ -2329,8 +2329,8 @@ packages:
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
|
||||
[email protected]5:
|
||||
resolution: {integrity: sha512-mOKxayErlaONK8fm4LN3XNd99Qu4plTpn9h9qf8wxzjGrJDzuD84FYzZ81HCd6ZsWp++VWVwOzL286Pf2s2u4A==}
|
||||
[email protected]6:
|
||||
resolution: {integrity: sha512-s/xTCZCxTFvHbAN1OzuhNaN5YPJH2ail0XAkctKW1b+RUAG4nUL5UHLXwNko1h8aEeT2jspBXegMgPJd8zcuag==}
|
||||
peerDependencies:
|
||||
'@nuxt/kit': '>=3.0.0'
|
||||
react: '>=17.0.0'
|
||||
@@ -2976,6 +2976,11 @@ packages:
|
||||
engines: {node: '>=18.0.0'}
|
||||
hasBin: true
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-8W675THjbzfFmLOQzjDBIBna+WjqMGIxmSZ1mMc1+o9qoVsEuAgQu5j5ueLhau8inOkDu9OslVg0FmfBs1RIHw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
hasBin: true
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==}
|
||||
engines: {node: '>=16.20.0'}
|
||||
@@ -4250,7 +4255,7 @@ snapshots:
|
||||
obug: 2.1.4
|
||||
std-env: 4.2.0
|
||||
tinyrainbow: 3.1.1
|
||||
vitest: 4.1.10(@types/[email protected])(@vitest/[email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected].5)([email protected]))
|
||||
vitest: 4.1.10(@types/[email protected])(@vitest/[email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected].8)([email protected]))
|
||||
|
||||
'@vitest/[email protected]':
|
||||
dependencies:
|
||||
@@ -4261,13 +4266,13 @@ snapshots:
|
||||
chai: 6.2.2
|
||||
tinyrainbow: 3.1.1
|
||||
|
||||
'@vitest/[email protected]([email protected](@types/[email protected])([email protected])([email protected])([email protected].5)([email protected]))':
|
||||
'@vitest/[email protected]([email protected](@types/[email protected])([email protected])([email protected])([email protected].8)([email protected]))':
|
||||
dependencies:
|
||||
'@vitest/spy': 4.1.10
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.21
|
||||
optionalDependencies:
|
||||
vite: 8.2.0(@types/[email protected])([email protected])([email protected])([email protected].5)([email protected])
|
||||
vite: 8.2.0(@types/[email protected])([email protected])([email protected])([email protected].8)([email protected])
|
||||
|
||||
'@vitest/[email protected]':
|
||||
dependencies:
|
||||
@@ -4607,7 +4612,7 @@ snapshots:
|
||||
yaml: 2.9.0
|
||||
zod: 4.4.3
|
||||
|
||||
[email protected]5([email protected]):
|
||||
[email protected]6([email protected]):
|
||||
optionalDependencies:
|
||||
react: 19.2.8
|
||||
|
||||
@@ -5238,6 +5243,12 @@ snapshots:
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
esbuild: 0.25.12
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
[email protected]:
|
||||
optionalDependencies:
|
||||
'@typescript/typescript-aix-ppc64': 7.0.2
|
||||
@@ -5296,7 +5307,7 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected](@types/[email protected])([email protected])([email protected])([email protected].5)([email protected]):
|
||||
[email protected](@types/[email protected])([email protected])([email protected])([email protected].8)([email protected]):
|
||||
dependencies:
|
||||
lightningcss: 1.33.0
|
||||
picomatch: 4.0.5
|
||||
@@ -5308,13 +5319,13 @@ snapshots:
|
||||
esbuild: 0.25.12
|
||||
fsevents: 2.3.3
|
||||
jiti: 2.7.0
|
||||
tsx: 4.23.5
|
||||
tsx: 4.23.8
|
||||
yaml: 2.9.0
|
||||
|
||||
[email protected](@types/[email protected])(@vitest/[email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected].5)([email protected])):
|
||||
[email protected](@types/[email protected])(@vitest/[email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected].8)([email protected])):
|
||||
dependencies:
|
||||
'@vitest/expect': 4.1.10
|
||||
'@vitest/mocker': 4.1.10([email protected](@types/[email protected])([email protected])([email protected])([email protected].5)([email protected]))
|
||||
'@vitest/mocker': 4.1.10([email protected](@types/[email protected])([email protected])([email protected])([email protected].8)([email protected]))
|
||||
'@vitest/pretty-format': 4.1.10
|
||||
'@vitest/runner': 4.1.10
|
||||
'@vitest/snapshot': 4.1.10
|
||||
@@ -5331,7 +5342,7 @@ snapshots:
|
||||
tinyexec: 1.3.0
|
||||
tinyglobby: 0.2.17
|
||||
tinyrainbow: 3.1.1
|
||||
vite: 8.2.0(@types/[email protected])([email protected])([email protected])([email protected].5)([email protected])
|
||||
vite: 8.2.0(@types/[email protected])([email protected])([email protected])([email protected].8)([email protected])
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@types/node': 26.1.2
|
||||
|
||||
@@ -0,0 +1,402 @@
|
||||
{
|
||||
"badge_name_Z75": "Training camp!",
|
||||
"badge_desc_Z75": "Per esser diventato un guerriero DOC!",
|
||||
"badge_name_Z67": "Coniglio Scaccia-Mito",
|
||||
"badge_desc_Z67": "La Sicurezza in Habbo non è una leggenda!",
|
||||
"badge_name_Z64": "Topo da Laboratorio",
|
||||
"badge_desc_Z64": "Beta Tester II",
|
||||
"badge_name_Z63": "Topo da Laboratorio",
|
||||
"badge_desc_Z63": "Beta Tester I",
|
||||
"badge_name_Z39": "Hotel Bau",
|
||||
"badge_desc_Z39": "Tutti a casa finalmente!",
|
||||
"badge_name_Z38": "Direttore Hotel Bau",
|
||||
"badge_desc_Z38": "Hotel a ***3 stelle",
|
||||
"badge_name_Z37": "Direttore Hotel Bau",
|
||||
"badge_desc_Z37": "Hotel a **2 stelle",
|
||||
"badge_name_Z36": "Direttore Hotel Bau",
|
||||
"badge_desc_Z36": "Hotel a *1 stella",
|
||||
"badge_name_Z35": "Hotel Bau",
|
||||
"badge_desc_Z35": "Per aver ritrovato i cuccioli",
|
||||
"badge_name_Z26": "Futuro Re",
|
||||
"badge_desc_Z26": "Sarai tu il prossimo Re?",
|
||||
"badge_name_Z16": "Campagna di Sicurezza Gennaio 2017",
|
||||
"badge_desc_Z16": "Per essere un esperto nel proteggere il mio account!",
|
||||
"badge_name_Z09": "In Campeggio",
|
||||
"badge_desc_Z09": "Campagna Campeggio con i Lupetti - 2009",
|
||||
"badge_name_Z05": "Ex Habbo eXpert",
|
||||
"badge_desc_Z05": "Gli Habbo X erano guide volontarie scelte dello Staff tra il 2006 e il 2008.",
|
||||
"badge_name_Z02": "W la Terra",
|
||||
"badge_desc_Z02": "Per i veri amanti della natura!",
|
||||
"badge_name_Z01": "Wow or not?",
|
||||
"badge_desc_Z01": "Hai impressionato la giuria con la tua sfilata!",
|
||||
"badge_name_YAK": "x",
|
||||
"badge_desc_YAK": "x",
|
||||
"badge_name_XXX": "x",
|
||||
"badge_desc_XXX": "x",
|
||||
"badge_name_XRLTD": "Distintivo Palla di Vetro con Neve",
|
||||
"badge_desc_XRLTD": "Per avere acquistato questo Raro LTD di Natale",
|
||||
"badge_name_XMSR1": "Macchina Magica",
|
||||
"badge_desc_XMSR1": "Per comprare una Macchina Rara Magica",
|
||||
"badge_name_XMS14": "Gnomi all'avventura!",
|
||||
"badge_desc_XMS14": "Per aver vinto la Competizione Video!",
|
||||
"badge_name_XMS13": "Cin cin!",
|
||||
"badge_desc_XMS13": "Attento a non versartelo addosso",
|
||||
"badge_name_XMS12": "Organizzatore di Feste",
|
||||
"badge_desc_XMS12": "... da urlo!",
|
||||
"badge_name_XMS10": "Postazione di Controllo",
|
||||
"badge_desc_XMS10": "Per comprare una Postazione di Controllo Rara",
|
||||
"badge_name_XMS07": "Babbo Natale e i 3 Re Magi",
|
||||
"badge_desc_XMS07": "Babbo Natale ha molte conoscenze...",
|
||||
"badge_name_XMS06": "Natale in Europa",
|
||||
"badge_desc_XMS06": "Un Natale dei più tipici!",
|
||||
"badge_name_XMS05": "Natale sulla Spiaggia",
|
||||
"badge_desc_XMS05": "Festeggia come un Brasiliano!",
|
||||
"badge_name_XMS04": "Natale senza Natale",
|
||||
"badge_desc_XMS04": "Festeggia come in Turchia",
|
||||
"badge_name_XMS03": "Decoratore di Alberi Natalizi",
|
||||
"badge_desc_XMS03": "Per avere decorato l'Albero di Natale Habbo 2013",
|
||||
"badge_name_XMS01": "Bottega di Babbo Natale",
|
||||
"badge_desc_XMS01": "Per avere acquistato l'Affare Stanza natalizio",
|
||||
"badge_name_XMH20": "Pupazzo Gatto Scheletrico",
|
||||
"badge_desc_XMH20": "Per avere acquistato il Pupazzo Gatto Scheletrico",
|
||||
"badge_name_XMH19": "Pupazzo Tristo Mietitore",
|
||||
"badge_desc_XMH19": "Per avere acquistato il Pupazzo Tristo Mietitore",
|
||||
"badge_name_XMH18": "Pupazzo Punk Rock",
|
||||
"badge_desc_XMH18": "Per avere acquistato il Pupazzo Punk Rock",
|
||||
"badge_name_XMH17": "Pupazzo Ragazza Habbo",
|
||||
"badge_desc_XMH17": "Per avere acquistato il Pupazzo Ragazza Habbo",
|
||||
"badge_name_XMH16": "Pupazzo Lucha Libre",
|
||||
"badge_desc_XMH16": "Per avere acquistato il Pupazzo Lucha Libre",
|
||||
"badge_name_XMH15": "Pupazzo Scheletro Tatuato",
|
||||
"badge_desc_XMH15": "Per avere acquistato il Pupazzo Scheletro Tatuato",
|
||||
"badge_name_XMH14": "Pupazzo Ragazzo Habbo",
|
||||
"badge_desc_XMH14": "Per avere acquistato il Pupazzo Ragazzo Habbo",
|
||||
"badge_name_XMH13": "Pupazzo in Giacca e Cravatta",
|
||||
"badge_desc_XMH13": "Per avere acquistato il Pupazzo in Giacca e Cravatta",
|
||||
"badge_name_XMH12": "Pupazzo Catrina",
|
||||
"badge_desc_XMH12": "Per avere acquistato il Pupazzo Catrina",
|
||||
"badge_name_XMH11": "Pupazzo Mariachi",
|
||||
"badge_desc_XMH11": "Per avere acquistato il Pupazzo Mariachi",
|
||||
"badge_name_XMB": "Mr.Pupazzo",
|
||||
"badge_desc_XMB": "Con cilindro e carota è il signore delle nevi!",
|
||||
"badge_name_XmasRoom_Top100": "Top 100 Castello di Natale",
|
||||
"badge_desc_XmasRoom_Top100": "Per essere rientrat@ nella top 100 della Competizione Stanza Castello di Natale",
|
||||
"badge_name_XmasRoom_Top10": "Top 10 Castello di Natale",
|
||||
"badge_desc_XmasRoom_Top10": "Per essere rientrat@ nella top 10 della Competizione Stanza Castello di Natale",
|
||||
"badge_name_XmasRoom": "Partecipante Castello di Natale",
|
||||
"badge_desc_XmasRoom": "Per aver partecipato alla Competizione Stanza Castello di Natale",
|
||||
"badge_name_XMAS2": "Ninnolo",
|
||||
"badge_desc_XMAS2": "Livello III",
|
||||
"badge_name_XMAS1": "Ninnolo",
|
||||
"badge_desc_XMAS1": "Livello II",
|
||||
"badge_name_XMAS0": "Ninnolo",
|
||||
"badge_desc_XMAS0": "Livello I",
|
||||
"badge_name_XMA": "Smile Congelato",
|
||||
"badge_desc_XMA": "Il suo sorriso ti scalda il cuore",
|
||||
"badge_name_XM9": "Palla di Neve",
|
||||
"badge_desc_XM9": "Deve ancora fare parecchia strada per diventare un pupazzo!",
|
||||
"badge_name_XM8": "Boccale di Birra",
|
||||
"badge_desc_XM8": "Habbo Natale 2007",
|
||||
"badge_name_XM7": "Moneta",
|
||||
"badge_desc_XM7": "Habbo Natale 2007",
|
||||
"badge_name_XM6": "Robot di Santa 3000",
|
||||
"badge_desc_XM6": "Habbo Natale 2007",
|
||||
"badge_name_XM5": "Fiocco di Neve",
|
||||
"badge_desc_XM5": "Habbo Natale 2007",
|
||||
"badge_name_XM4": "Natale 2006",
|
||||
"badge_desc_XM4": "Ero su Habbo a Natale del 2006!",
|
||||
"badge_name_XM3": "Renna",
|
||||
"badge_desc_XM3": "Habbo Natale 2005",
|
||||
"badge_name_XM2": "DJ-Bling",
|
||||
"badge_desc_XM2": "Habbo Natale 2005",
|
||||
"badge_name_XM10E": "FREEZE!",
|
||||
"badge_desc_XM10E": "Hai costruito un'Arena Fantastica!",
|
||||
"badge_name_XM10D": "Campione Wired-Freeze",
|
||||
"badge_desc_XM10D": "L'ennesima potenza del divertimento!",
|
||||
"badge_name_XM10C": "Regalo Natalizio Fuori Stagione",
|
||||
"badge_desc_XM10C": "Saluti dai Caraibi!",
|
||||
"badge_name_XM10B": "Pattino d'Oro",
|
||||
"badge_desc_XM10B": "Il Miglior Palazzetto del Ghiaccio",
|
||||
"badge_name_XM10A": "Città Natalizia!",
|
||||
"badge_desc_XM10A": "Per gli addobbi Natalizi più Originali!",
|
||||
"badge_name_XM1": "Rasta Santa",
|
||||
"badge_desc_XM1": "Habbo Natale 2005 e 2006",
|
||||
"badge_name_XGH1": "Il Fantasma del Natale",
|
||||
"badge_desc_XGH1": "Ho trovato il Fantasma del Natale",
|
||||
"badge_name_X535": "Conduttore di slitta - Sig.ra Claus",
|
||||
"badge_desc_X535": "Oh, che bello andare su una slitta trainata da un cavallo..ehi!",
|
||||
"badge_name_X534": "Conduttore di slitta - Habbo Natale",
|
||||
"badge_desc_X534": "Oh, che bello andare su una slitta trainata da un cavallo..ehi!",
|
||||
"badge_name_X533": "Pranzo di Natale - Sig.ra Claus",
|
||||
"badge_desc_X533": "Non c'è di niente di meglio di un bel Pranzo di Natale",
|
||||
"badge_name_X532": "Pranzo di Natale - Habbo Natale",
|
||||
"badge_desc_X532": "Non c'è di niente di meglio di un bel Pranzo di Natale",
|
||||
"badge_name_X531": "Silent night - Sig.ra Claus",
|
||||
"badge_desc_X531": "Non hai svegliato nessun Habbo, hai sfiorato la perfezione come la Sig.ra Claus",
|
||||
"badge_name_X530": "Silent night - Habbo Natale",
|
||||
"badge_desc_X530": "Non hai svegliato nessun Habbo, hai sfiorato la perfezione come Habbo Natale",
|
||||
"badge_name_X529": "Preparazione Regali di Natale - Sig.ra Claus",
|
||||
"badge_desc_X529": "Ti sei assicurato che quest'anno tutti ricevano il giusto regalo di Natale",
|
||||
"badge_name_X528": "Preparazione Regali di Natale - Habbo Natale",
|
||||
"badge_desc_X528": "Ti sei assicurato che quest'anno tutti ricevano il giusto regalo di Natale",
|
||||
"badge_name_X527": "Mercato di Natale - Sig.ra Claus",
|
||||
"badge_desc_X527": "Per aver completato il labirinto del mercato di Natale",
|
||||
"badge_name_X526": "Mercato di Natale - Habbo Natale",
|
||||
"badge_desc_X526": "Per aver completato il labirinto del mercato di Natale",
|
||||
"badge_name_X525": "Proprietario di Renna - Sig.ra Claus",
|
||||
"badge_desc_X525": "Vixen, la renna della Sig.ra Claus, è stata riportata sana e salva nella stalla",
|
||||
"badge_name_X524": "Proprietario di Renna - Habbo Natale",
|
||||
"badge_desc_X524": "Dasher, la renna preferita di Habbo Natale, è stata riportata sana e salva nella stalla",
|
||||
"badge_name_X523": "Film di Natale - Sig.ra Claus",
|
||||
"badge_desc_X523": "Sei un vero Fan dei film di Natale",
|
||||
"badge_name_X522": "Film di Natale - Habbo Natale",
|
||||
"badge_desc_X522": "Sei un vero Fan dei film di Natale",
|
||||
"badge_name_X521": "Meraviglioso Albero di Natale - Sig.ra Natale",
|
||||
"badge_desc_X521": "Oh Albero di Natale, Oh Albero di Natale, Le tue foglie son così immutabili",
|
||||
"badge_name_X520": "Meraviglioso Albero di Natale - Habbo Natale",
|
||||
"badge_desc_X520": "Oh Albero di Natale, Oh Albero di Natale, Le tue foglie son così immutabili",
|
||||
"badge_name_X519": "Canti Natalizi - Sig.ra Claus",
|
||||
"badge_desc_X519": "Jingle bell, jingle bell, jingle bell rock",
|
||||
"badge_name_X518": "Canti Natalizi - Habbo Natale",
|
||||
"badge_desc_X518": "Haaaabbo Natale sta arrivando in città!",
|
||||
"badge_name_X2535": "L'Alimentatore Preferito di Esophagor",
|
||||
"badge_desc_X2535": "",
|
||||
"badge_name_X2534": "Stazione dei Treni",
|
||||
"badge_desc_X2534": "",
|
||||
"badge_name_X2533": "Collezione Paese delle Meraviglie di Cacao",
|
||||
"badge_desc_X2533": "",
|
||||
"badge_name_X2532": "Affare Stanza Ufficio di Habbo Natale",
|
||||
"badge_desc_X2532": "",
|
||||
"badge_name_X2531": "Affare Stanza Natale Accogliente di Habbo",
|
||||
"badge_desc_X2531": "",
|
||||
"badge_name_X2530": "Uovo Glassato LTD",
|
||||
"badge_desc_X2530": "",
|
||||
"badge_name_X2529": "Borsa Kitty Rara",
|
||||
"badge_desc_X2529": "",
|
||||
"badge_name_X2528": "Cuscino di Ghiaccio Raro",
|
||||
"badge_desc_X2528": "",
|
||||
"badge_name_X2527": "Habbo Night Hotel Raro",
|
||||
"badge_desc_X2527": "",
|
||||
"badge_name_X2526": "Coda di cavallo Ghiacciata Scintillante Rara",
|
||||
"badge_desc_X2526": "",
|
||||
"badge_name_X2525": "Buon Natale 2025!",
|
||||
"badge_desc_X2525": "",
|
||||
"badge_name_X2521": "A caccia di vacanze!",
|
||||
"badge_desc_X2521": "",
|
||||
"badge_name_X2520": "Alimentatore di Esophagor",
|
||||
"badge_desc_X2520": "",
|
||||
"badge_name_X2518": "Battaglia con Palle di neve!",
|
||||
"badge_desc_X2518": "",
|
||||
"badge_name_X2516": "Spirito delle Feste",
|
||||
"badge_desc_X2516": "",
|
||||
"badge_name_X2515": "Straordinario Creatore delle Feste",
|
||||
"badge_desc_X2515": "",
|
||||
"badge_name_X2514": "Felice & Moderno",
|
||||
"badge_desc_X2514": "",
|
||||
"badge_name_X2513": "Eroe delle Tradizioni Natalizie",
|
||||
"badge_desc_X2513": "",
|
||||
"badge_name_X2512": "Corona dello Scontro di Natale",
|
||||
"badge_desc_X2512": "",
|
||||
"badge_name_X2511": "Habubu Glam",
|
||||
"badge_desc_X2511": "",
|
||||
"badge_name_X2510": "Habubu Cozy",
|
||||
"badge_desc_X2510": "",
|
||||
"badge_name_X2509": "Habubu Dream",
|
||||
"badge_desc_X2509": "",
|
||||
"badge_name_X2508": "Habubu Calm",
|
||||
"badge_desc_X2508": "",
|
||||
"badge_name_X2507": "Habubu Luck",
|
||||
"badge_desc_X2507": "",
|
||||
"badge_name_X2506": "Habubu Hope",
|
||||
"badge_desc_X2506": "",
|
||||
"badge_name_X2505": "Habubu Happy",
|
||||
"badge_desc_X2505": "",
|
||||
"badge_name_X2504": "Habubu Fun",
|
||||
"badge_desc_X2504": "",
|
||||
"badge_name_X2503": "Habubu Love",
|
||||
"badge_desc_X2503": "",
|
||||
"badge_name_X2502": "Squadra - DJ Bling",
|
||||
"badge_desc_X2502": "",
|
||||
"badge_name_X2501": "Squadra - Rasta Santa",
|
||||
"badge_desc_X2501": "",
|
||||
"badge_name_X2330": "Competizione Anatroccolo",
|
||||
"badge_desc_X2330": "",
|
||||
"badge_name_X2329": "Competizione Stanza Sweet Suite NL, TR",
|
||||
"badge_desc_X2329": "",
|
||||
"badge_name_X2328": "Offerta Crafting",
|
||||
"badge_desc_X2328": "",
|
||||
"badge_name_X2327": "Trono Bianco",
|
||||
"badge_desc_X2327": "",
|
||||
"badge_name_X2326": "Buon 2024!",
|
||||
"badge_desc_X2326": "",
|
||||
"badge_name_X2325": "Buon Natale!",
|
||||
"badge_desc_X2325": "",
|
||||
"badge_name_X2324": "Goditi il Natale Habbo",
|
||||
"badge_desc_X2324": "",
|
||||
"badge_name_X2323": "Aiuta Frank Wobbah a cucinare",
|
||||
"badge_desc_X2323": "",
|
||||
"badge_name_X2322": "Salvataggio delle ricette di Frank Wobbah",
|
||||
"badge_desc_X2322": "",
|
||||
"badge_name_X2321": "Missione Babbo Natale per un giorno",
|
||||
"badge_desc_X2321": "",
|
||||
"badge_name_X2320": "Missione Brilla come un Biglietto d'oro",
|
||||
"badge_desc_X2320": "",
|
||||
"badge_name_X2319": "Missione Delizie Arcobaleno",
|
||||
"badge_desc_X2319": "",
|
||||
"badge_name_X2318": "Missione Frutti di Bosco",
|
||||
"badge_desc_X2318": "",
|
||||
"badge_name_X2317": "Missione Mentine Rinfrescanti",
|
||||
"badge_desc_X2317": "",
|
||||
"badge_name_X2316": "Pazzo per la Missione del Cioccolato",
|
||||
"badge_desc_X2316": "",
|
||||
"badge_name_X2315": "Torta Trono Rara",
|
||||
"badge_desc_X2315": "",
|
||||
"badge_name_X2314": "Offerta di Capodanno 2023",
|
||||
"badge_desc_X2314": "",
|
||||
"badge_name_X2313": "Offerta Indumenti Natale 2023",
|
||||
"badge_desc_X2313": "",
|
||||
"badge_name_X2312": "Carosello di Cioccolato Artigianale LTD",
|
||||
"badge_desc_X2312": "",
|
||||
"badge_name_X2311": "Corona d'Oro 24K LTD",
|
||||
"badge_desc_X2311": "",
|
||||
"badge_name_X2310": "Ali sulla Testa Mitiche Rare",
|
||||
"badge_desc_X2310": "",
|
||||
"badge_name_X2309": "Seduta Aerea Rara",
|
||||
"badge_desc_X2309": "",
|
||||
"badge_name_X2308": "Nastro Scartami RARO",
|
||||
"badge_desc_X2308": "",
|
||||
"badge_name_X2307": "Albero di Zucchero Filato RARO",
|
||||
"badge_desc_X2307": "",
|
||||
"badge_name_X2306": "Affare Stanza di Capodanno",
|
||||
"badge_desc_X2306": "",
|
||||
"badge_name_X2305": "Affare Stanza Pista di Pattinaggio sul Ghiaccio",
|
||||
"badge_desc_X2305": "",
|
||||
"badge_name_X2304": "Affare Stanza Ritrovo dell'Elfo",
|
||||
"badge_desc_X2304": "",
|
||||
"badge_name_X2303": "Bottega Moderna di Babbo Natale",
|
||||
"badge_desc_X2303": "",
|
||||
"badge_name_X2302": "Emporio del Cioccolato Magico",
|
||||
"badge_desc_X2302": "",
|
||||
"badge_name_X2301": "Collezione Paese delle Meraviglie di Cacao",
|
||||
"badge_desc_X2301": "",
|
||||
"badge_name_X2234": "Sanrio Christmas Room Competition",
|
||||
"badge_desc_X2234": "",
|
||||
"badge_name_X2232": "Affare Stanza Salone da Pranzo",
|
||||
"badge_desc_X2232": "",
|
||||
"badge_name_X2231": "Offerta Bevande per soldi Reali",
|
||||
"badge_desc_X2231": "",
|
||||
"badge_name_X2230": "Renna Abile",
|
||||
"badge_desc_X2230": "",
|
||||
"badge_name_X2229": "Pinguino Abile",
|
||||
"badge_desc_X2229": "",
|
||||
"badge_name_X2228": "Labirinto dei Pinguini: Livello Facile - Completato",
|
||||
"badge_desc_X2228": "",
|
||||
"badge_name_X2227": "Labirinto dei Pinguini: Livello Medio - Completato",
|
||||
"badge_desc_X2227": "",
|
||||
"badge_name_X2226": "Labirinto dei Pinguini: Livello Folle - Completato",
|
||||
"badge_desc_X2226": "",
|
||||
"badge_name_X2224": "Natale Habbo 2022 - Pinguino di Tokyo",
|
||||
"badge_desc_X2224": "",
|
||||
"badge_name_X2223": "Natale Habbo 2022 - Pinguino Timido",
|
||||
"badge_desc_X2223": "",
|
||||
"badge_name_X2222": "Natale Habbo 2022 - Pinguino Accademico",
|
||||
"badge_desc_X2222": "",
|
||||
"badge_name_X2220": "Natale Habbo 2022 - Pinguino Unicorno",
|
||||
"badge_desc_X2220": "",
|
||||
"badge_name_X2219": "Natale Habbo 2022 - Pinguino Albino",
|
||||
"badge_desc_X2219": "",
|
||||
"badge_name_X2218": "Natale Habbo 2022 - Pinguino Fantasma",
|
||||
"badge_desc_X2218": "",
|
||||
"badge_name_X2217": "Natale Habbo 2022 - Pinguino Egizio",
|
||||
"badge_desc_X2217": "",
|
||||
"badge_name_X2216": "Natale Habbo 2022 - Pinguino Artista",
|
||||
"badge_desc_X2216": "",
|
||||
"badge_name_X2215": "Natale Habbo 2022 - Pinguino Addormentato",
|
||||
"badge_desc_X2215": "",
|
||||
"badge_name_X2214": "Natale Habbo 2022 - Pinguino Testa a Molla",
|
||||
"badge_desc_X2214": "",
|
||||
"badge_name_X2213": "Offerta Cibo Soldi Veri",
|
||||
"badge_desc_X2213": "",
|
||||
"badge_name_X2212": "Uovo Habberge Palla di Neve LTD",
|
||||
"badge_desc_X2212": "",
|
||||
"badge_name_X2211": "Ali d'Angelo LTD",
|
||||
"badge_desc_X2211": "",
|
||||
"badge_name_X2210": "RARO Husky Addestrato",
|
||||
"badge_desc_X2210": "",
|
||||
"badge_name_X2209": "Raro Sauna Nordica",
|
||||
"badge_desc_X2209": "",
|
||||
"badge_name_X2208": "RARO Capelli con Treccine",
|
||||
"badge_desc_X2208": "",
|
||||
"badge_name_X2207": "Raro Look da Albero Festivo",
|
||||
"badge_desc_X2207": "",
|
||||
"badge_name_X2206": "Affare Stanza Sauna Finlandese",
|
||||
"badge_desc_X2206": "",
|
||||
"badge_name_X2205": "Affare Stanza Snowboard sulle Alpi in Inverno",
|
||||
"badge_desc_X2205": "",
|
||||
"badge_name_X2204": "Affare Stanza Natale Bavarese",
|
||||
"badge_desc_X2204": "",
|
||||
"badge_name_X2203": "Affare Stanza Inverno ad Habbo Stonehenge",
|
||||
"badge_desc_X2203": "",
|
||||
"badge_name_X2202": "Affare Stanza Baita del Resort Invernale",
|
||||
"badge_desc_X2202": "",
|
||||
"badge_name_X2201": "Affare Stanza Resort degli Sport Invernali",
|
||||
"badge_desc_X2201": "",
|
||||
"badge_name_X2139": "Buon Natale!",
|
||||
"badge_desc_X2139": "",
|
||||
"badge_name_X2138": "Sulla lista dei buoni di Babbo Natale!",
|
||||
"badge_desc_X2138": "",
|
||||
"badge_name_X2137": "La Squadra di Babbo Natale",
|
||||
"badge_desc_X2137": "",
|
||||
"badge_name_X2136": "Chi è il vero Babbo Natale?",
|
||||
"badge_desc_X2136": "",
|
||||
"badge_name_X2135": "Il Coro Natalizio di Habbo",
|
||||
"badge_desc_X2135": "",
|
||||
"badge_name_X2134": "Battaglia di Palle di Neve!",
|
||||
"badge_desc_X2134": "",
|
||||
"badge_name_X2133": "Oh Albero di Natale",
|
||||
"badge_desc_X2133": "",
|
||||
"badge_name_X2132": "Si prepara una Tempesta!",
|
||||
"badge_desc_X2132": "",
|
||||
"badge_name_X2131": "Pattinando sul Ghiaccio Sottile",
|
||||
"badge_desc_X2131": "",
|
||||
"badge_name_X2130": "Piante Malridotte dall'Inverno",
|
||||
"badge_desc_X2130": "",
|
||||
"badge_name_X2129": "Nobile Uccello d'Oro",
|
||||
"badge_desc_X2129": "",
|
||||
"badge_name_X2128": "Caso Risolto!",
|
||||
"badge_desc_X2128": "",
|
||||
"badge_name_X2127": "Passi nella neve",
|
||||
"badge_desc_X2127": "",
|
||||
"badge_name_X2126": "La Grande Fuga",
|
||||
"badge_desc_X2126": "",
|
||||
"badge_name_X2125": "Il Sospetto Sbagliato",
|
||||
"badge_desc_X2125": "",
|
||||
"badge_name_X2124": "Scappato appena in tempo!",
|
||||
"badge_desc_X2124": "",
|
||||
"badge_name_X2123": "Identità Segreta",
|
||||
"badge_desc_X2123": "",
|
||||
"badge_name_X2122": "Una Mappa Segreta",
|
||||
"badge_desc_X2122": "",
|
||||
"badge_name_X2121": "Zzzz..Zzzz..",
|
||||
"badge_desc_X2121": "",
|
||||
"badge_name_X2120": "Si è intrufolato nella stanza del commesso viaggiatore",
|
||||
"badge_desc_X2120": "",
|
||||
"badge_name_X2119": "Un Look strepitoso!",
|
||||
"badge_desc_X2119": "",
|
||||
"badge_name_X2118": "Chiavi prestate",
|
||||
"badge_desc_X2118": "",
|
||||
"badge_name_X2117": "Il Treno sta arrivando",
|
||||
"badge_desc_X2117": "",
|
||||
"badge_name_X2116": "Bandito del Treno",
|
||||
"badge_desc_X2116": "",
|
||||
"badge_name_X2115": "Il Treno Habbo Express",
|
||||
"badge_desc_X2115": "",
|
||||
"badge_name_X2114": "Uovo Habberge Art Deco LTD",
|
||||
"badge_desc_X2114": "",
|
||||
"badge_name_X2113": "Locomotiva a Vapore Rara",
|
||||
"badge_desc_X2113": "",
|
||||
"badge_name_X2112": "Costume da Treno Raro",
|
||||
"badge_desc_X2112": "",
|
||||
"badge_name_X2111": "Macchina della Cioccolata Calda di Lusso Rara",
|
||||
"badge_desc_X2111": ""
|
||||
}
|
||||
@@ -18,7 +18,15 @@ import { toast } from "sonner";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { adminFetch } from "@/lib/admin-fetch";
|
||||
import { HABBO_GAMEDATA_HOTELS } from "@/lib/habbo-gamedata-hotel";
|
||||
import { runSseImport } from "@/lib/sse-client";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
@@ -30,6 +38,7 @@ interface CloneSource {
|
||||
furnidataUrl: string;
|
||||
nitroBaseUrl: string;
|
||||
iconBaseUrl: string;
|
||||
hotel: string;
|
||||
}
|
||||
|
||||
interface FurniItem {
|
||||
@@ -70,6 +79,7 @@ function SourceForm({ initial, onSave, onCancel, saving }: SourceFormProps) {
|
||||
const [furnidataUrl, setFurnidataUrl] = useState(initial?.furnidataUrl ?? "");
|
||||
const [nitroBaseUrl, setNitroBaseUrl] = useState(initial?.nitroBaseUrl ?? "");
|
||||
const [iconBaseUrl, setIconBaseUrl] = useState(initial?.iconBaseUrl ?? "");
|
||||
const [hotel, setHotel] = useState(initial?.hotel ?? "");
|
||||
|
||||
async function handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
@@ -79,6 +89,7 @@ function SourceForm({ initial, onSave, onCancel, saving }: SourceFormProps) {
|
||||
furnidataUrl,
|
||||
nitroBaseUrl,
|
||||
iconBaseUrl,
|
||||
hotel,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -115,6 +126,19 @@ function SourceForm({ initial, onSave, onCancel, saving }: SourceFormProps) {
|
||||
onChange={(e) => setIconBaseUrl(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<Select value={hotel} onValueChange={setHotel}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Hotel (optional)" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="">None</SelectItem>
|
||||
{HABBO_GAMEDATA_HOTELS.map((h) => (
|
||||
<SelectItem key={h.value} value={h.value}>
|
||||
{h.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button type="submit" size="sm" disabled={saving}>
|
||||
@@ -261,6 +285,12 @@ function SourcesManager({
|
||||
<div className="text-xs text-muted-foreground truncate">
|
||||
{src.furnidataUrl}
|
||||
</div>
|
||||
{src.hotel ? (
|
||||
<div className="text-[10px] text-muted-foreground/70">
|
||||
{HABBO_GAMEDATA_HOTELS.find((h) => h.value === src.hotel)
|
||||
?.label ?? `habbo.${src.hotel}`}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</button>
|
||||
<div className="flex items-center gap-1 shrink-0">
|
||||
|
||||
@@ -3,11 +3,11 @@ import path from "node:path";
|
||||
import { withAdmin } from "@/lib/api-handler";
|
||||
import { apiError, apiOk } from "@/lib/api-response";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { getGamedataRoot } from "@/lib/services/furni-asset-dirs";
|
||||
|
||||
const EXTERNAL_TEXTS_PATH = path.join(
|
||||
process.cwd(),
|
||||
"public/nitro-assets/gamedata/ExternalTexts.json",
|
||||
);
|
||||
function externalTextsPath(gamedataRoot: string): string {
|
||||
return path.join(gamedataRoot, "config", "ExternalTexts.json");
|
||||
}
|
||||
|
||||
/** GET - Read badge name/desc from ExternalTexts.json */
|
||||
export const GET = withAdmin(
|
||||
@@ -16,8 +16,11 @@ export const GET = withAdmin(
|
||||
const code = request.nextUrl.searchParams.get("code")?.toUpperCase();
|
||||
if (!code) return apiError("Missing code", 400);
|
||||
|
||||
const gamedataRoot = await getGamedataRoot();
|
||||
if (!gamedataRoot) return apiError("gamedata_root not configured", 500);
|
||||
|
||||
try {
|
||||
const raw = await fs.readFile(EXTERNAL_TEXTS_PATH, "utf-8");
|
||||
const raw = await fs.readFile(externalTextsPath(gamedataRoot), "utf-8");
|
||||
const texts: Record<string, string> = JSON.parse(raw);
|
||||
|
||||
const name =
|
||||
|
||||
@@ -4,13 +4,13 @@ import { withAdmin } from "@/lib/api-handler";
|
||||
import { apiError, apiOk } from "@/lib/api-response";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { logAudit } from "@/lib/services/audit";
|
||||
import { getGamedataRoot } from "@/lib/services/furni-asset-dirs";
|
||||
import { searchBadges } from "@/lib/services/habboassets";
|
||||
import { importBadgeSynced } from "@/lib/services/import-badge";
|
||||
|
||||
const EXTERNAL_TEXTS_PATH = path.join(
|
||||
process.cwd(),
|
||||
"public/nitro-assets/gamedata/ExternalTexts.json",
|
||||
);
|
||||
function externalTextsPath(gamedataRoot: string): string {
|
||||
return path.join(gamedataRoot, "config", "ExternalTexts.json");
|
||||
}
|
||||
|
||||
// ── Search badges via official Habbo external texts ────────────────
|
||||
export const GET = withAdmin(
|
||||
@@ -33,11 +33,14 @@ export const GET = withAdmin(
|
||||
// Check which badges already exist in ExternalTexts.json
|
||||
const existingCodes = new Set<string>();
|
||||
try {
|
||||
const raw = await fs.readFile(EXTERNAL_TEXTS_PATH, "utf-8");
|
||||
const texts = JSON.parse(raw);
|
||||
for (const badge of badges) {
|
||||
if (texts[`badge_name_${badge.code}`] !== undefined) {
|
||||
existingCodes.add(badge.code);
|
||||
const gamedataRoot = await getGamedataRoot();
|
||||
if (gamedataRoot) {
|
||||
const raw = await fs.readFile(externalTextsPath(gamedataRoot), "utf-8");
|
||||
const texts = JSON.parse(raw);
|
||||
for (const badge of badges) {
|
||||
if (texts[`badge_name_${badge.code}`] !== undefined) {
|
||||
existingCodes.add(badge.code);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
|
||||
@@ -76,7 +76,7 @@ export const POST = withAdmin(
|
||||
{ permission: PERMS.ASSETS_IMPORT },
|
||||
async (request, ctx) => {
|
||||
const body = await request.json();
|
||||
const { name, furnidataUrl, nitroBaseUrl, iconBaseUrl, id } =
|
||||
const { name, furnidataUrl, nitroBaseUrl, iconBaseUrl, hotel, id } =
|
||||
body as Record<string, string>;
|
||||
|
||||
const isHttps = (u: string) => {
|
||||
@@ -104,6 +104,7 @@ export const POST = withAdmin(
|
||||
furnidataUrl,
|
||||
nitroBaseUrl,
|
||||
iconBaseUrl,
|
||||
hotel: hotel ?? "",
|
||||
});
|
||||
|
||||
logAudit({
|
||||
|
||||
@@ -183,8 +183,8 @@ export async function runCatalogAudit(
|
||||
}
|
||||
})(),
|
||||
]);
|
||||
const iconSet = new Set(iconFiles);
|
||||
const nitroSet = new Set(nitroFiles);
|
||||
let iconSet = new Set(iconFiles);
|
||||
let nitroSet = new Set(nitroFiles);
|
||||
// Badge detection: `items_base.type = 'b'` is authoritative. The album1584
|
||||
// `.gif` listing is kept as a fallback for hotels that store badges with a
|
||||
// non-standard type.
|
||||
@@ -466,6 +466,15 @@ export async function runCatalogAudit(
|
||||
}
|
||||
});
|
||||
|
||||
// Re-read directories so the set reflects files written
|
||||
// by the repair pass.
|
||||
const [repairedIconFiles, repairedNitroFiles] = await Promise.all([
|
||||
readdir(iconDir).catch(() => [] as string[]),
|
||||
readdir(nitroDir).catch(() => [] as string[]),
|
||||
]);
|
||||
iconSet = new Set(repairedIconFiles);
|
||||
nitroSet = new Set(repairedNitroFiles);
|
||||
|
||||
// Re-check which of the previously-missing icons are still missing
|
||||
// and drop the ones the repair fixed from the issue list. The ones
|
||||
// left over cannot be restored from any source → legacy/broken.
|
||||
@@ -530,6 +539,15 @@ export async function runCatalogAudit(
|
||||
}
|
||||
});
|
||||
|
||||
// Re-read directories so the set reflects files written
|
||||
// by the repair pass.
|
||||
const [repairedIconFiles2, repairedNitroFiles2] = await Promise.all([
|
||||
readdir(iconDir).catch(() => [] as string[]),
|
||||
readdir(nitroDir).catch(() => [] as string[]),
|
||||
]);
|
||||
iconSet = new Set(repairedIconFiles2);
|
||||
nitroSet = new Set(repairedNitroFiles2);
|
||||
|
||||
// Re-check which of the previously-missing nitros are still missing
|
||||
// and drop the ones the repair fixed from the issue list. The ones
|
||||
// left over cannot be restored from any source → legacy/broken.
|
||||
|
||||
@@ -85,6 +85,7 @@ const SOURCE = {
|
||||
furnidataUrl: "https://x/fd.json",
|
||||
nitroBaseUrl: "https://x/furni",
|
||||
iconBaseUrl: "https://x/icons",
|
||||
hotel: "",
|
||||
};
|
||||
const ENTRY = {
|
||||
id: 5,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { eq, inArray, sql } from "drizzle-orm";
|
||||
import { db, ItemsBase } from "@/lib/db";
|
||||
import { HABBO_GAMEDATA_HOTEL_SETTING_KEY } from "@/lib/habbo-gamedata-hotel";
|
||||
import { logger } from "@/lib/logger";
|
||||
import { extractFurniIconPng } from "@/lib/services/clone-icon";
|
||||
import type { CloneSource } from "@/lib/services/clone-sources";
|
||||
@@ -16,6 +17,7 @@ import {
|
||||
} from "@/lib/services/furni-import";
|
||||
import { browserHeaders } from "@/lib/services/import/core/browser-headers";
|
||||
import { downloadFile } from "@/lib/services/import/core/download";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
import { parseNitroBundle } from "@/lib/services/swf/nitro-builder";
|
||||
|
||||
export interface SourceFurni {
|
||||
@@ -221,6 +223,15 @@ export async function cloneSingleFurni(params: {
|
||||
const { classname, itemType } = entry;
|
||||
const warnings: string[] = [];
|
||||
|
||||
// Set the active Habbo hotel for official furnidata enrichment.
|
||||
if (source.hotel) {
|
||||
try {
|
||||
await siteSettings.update(HABBO_GAMEDATA_HOTEL_SETTING_KEY, source.hotel);
|
||||
} catch {
|
||||
/* non-blocking */
|
||||
}
|
||||
}
|
||||
|
||||
// Path-traversal guard: classname comes from remote furnidata and is used to
|
||||
// build file paths — reject anything that doesn't look like a safe furni name.
|
||||
if (!/^[\w\-.*]+$/.test(classname)) {
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface CloneSource {
|
||||
furnidataUrl: string;
|
||||
nitroBaseUrl: string;
|
||||
iconBaseUrl: string;
|
||||
hotel: string;
|
||||
}
|
||||
|
||||
const KEY = "clone_sources";
|
||||
@@ -18,11 +19,76 @@ const KEY = "clone_sources";
|
||||
export const DEFAULT_SOURCES: CloneSource[] = [
|
||||
{
|
||||
id: "default-habbo",
|
||||
name: "Habbo",
|
||||
name: "Habbo (IT)",
|
||||
furnidataUrl:
|
||||
"https://raw.githubusercontent.com/Izzxt/habbo-resource/master/gamedata/furnidata.json",
|
||||
nitroBaseUrl: "",
|
||||
iconBaseUrl: "",
|
||||
hotel: "it",
|
||||
},
|
||||
{
|
||||
id: "default-habbo-com",
|
||||
name: "Habbo (COM)",
|
||||
furnidataUrl: "https://www.habbo.com/gamedata/furnidata_json/1",
|
||||
nitroBaseUrl: "",
|
||||
iconBaseUrl: "",
|
||||
hotel: "com",
|
||||
},
|
||||
{
|
||||
id: "default-habbo-nl",
|
||||
name: "Habbo (NL)",
|
||||
furnidataUrl: "https://www.habbo.nl/gamedata/furnidata_json/1",
|
||||
nitroBaseUrl: "",
|
||||
iconBaseUrl: "",
|
||||
hotel: "nl",
|
||||
},
|
||||
{
|
||||
id: "default-habbo-de",
|
||||
name: "Habbo (DE)",
|
||||
furnidataUrl: "https://www.habbo.de/gamedata/furnidata_json/1",
|
||||
nitroBaseUrl: "",
|
||||
iconBaseUrl: "",
|
||||
hotel: "de",
|
||||
},
|
||||
{
|
||||
id: "default-habbo-fr",
|
||||
name: "Habbo (FR)",
|
||||
furnidataUrl: "https://www.habbo.fr/gamedata/furnidata_json/1",
|
||||
nitroBaseUrl: "",
|
||||
iconBaseUrl: "",
|
||||
hotel: "fr",
|
||||
},
|
||||
{
|
||||
id: "default-habbo-es",
|
||||
name: "Habbo (ES)",
|
||||
furnidataUrl: "https://www.habbo.es/gamedata/furnidata_json/1",
|
||||
nitroBaseUrl: "",
|
||||
iconBaseUrl: "",
|
||||
hotel: "es",
|
||||
},
|
||||
{
|
||||
id: "default-habbo-fi",
|
||||
name: "Habbo (FI)",
|
||||
furnidataUrl: "https://www.habbo.fi/gamedata/furnidata_json/1",
|
||||
nitroBaseUrl: "",
|
||||
iconBaseUrl: "",
|
||||
hotel: "fi",
|
||||
},
|
||||
{
|
||||
id: "default-habbo-br",
|
||||
name: "Habbo (BR)",
|
||||
furnidataUrl: "https://www.habbo.com.br/gamedata/furnidata_json/1",
|
||||
nitroBaseUrl: "",
|
||||
iconBaseUrl: "",
|
||||
hotel: "com.br",
|
||||
},
|
||||
{
|
||||
id: "default-habbo-tr",
|
||||
name: "Habbo (TR)",
|
||||
furnidataUrl: "https://www.habbo.com.tr/gamedata/furnidata_json/1",
|
||||
nitroBaseUrl: "",
|
||||
iconBaseUrl: "",
|
||||
hotel: "com.tr",
|
||||
},
|
||||
{
|
||||
id: "default-wibbo",
|
||||
@@ -30,6 +96,7 @@ export const DEFAULT_SOURCES: CloneSource[] = [
|
||||
furnidataUrl: "https://assets.wibbo.org/gamedata/FurnitureData.json",
|
||||
nitroBaseUrl: "https://assets.wibbo.org/bundled/furniture",
|
||||
iconBaseUrl: "https://assets.wibbo.org/icons",
|
||||
hotel: "com",
|
||||
},
|
||||
{
|
||||
id: "default-hubba",
|
||||
@@ -37,6 +104,7 @@ export const DEFAULT_SOURCES: CloneSource[] = [
|
||||
furnidataUrl: "https://nitro.hubba.cc/gamedata/FurnitureData.json",
|
||||
nitroBaseUrl: "https://nitro.hubba.cc/bundled/furniture",
|
||||
iconBaseUrl: "",
|
||||
hotel: "com",
|
||||
},
|
||||
{
|
||||
id: "default-sodaho",
|
||||
@@ -44,6 +112,7 @@ export const DEFAULT_SOURCES: CloneSource[] = [
|
||||
furnidataUrl: "https://assets.sodaho.tel/gamedata/FurnitureData.json",
|
||||
nitroBaseUrl: "",
|
||||
iconBaseUrl: "",
|
||||
hotel: "com",
|
||||
},
|
||||
{
|
||||
id: "default-leet",
|
||||
@@ -51,6 +120,7 @@ export const DEFAULT_SOURCES: CloneSource[] = [
|
||||
furnidataUrl: "https://www.leet.ws/gamedata/furnidata.json",
|
||||
nitroBaseUrl: "https://www.leet.ws/nitro/bundled/furniture",
|
||||
iconBaseUrl: "https://www.leet.ws/nitro/icons",
|
||||
hotel: "com",
|
||||
},
|
||||
{
|
||||
id: "default-hubbly",
|
||||
@@ -58,6 +128,7 @@ export const DEFAULT_SOURCES: CloneSource[] = [
|
||||
furnidataUrl: "https://www.hubbly.pw/gamedata/furnidata.json",
|
||||
nitroBaseUrl: "https://www.hubbly.pw/nitro/bundled/furniture",
|
||||
iconBaseUrl: "https://www.hubbly.pw/nitro/icons",
|
||||
hotel: "com",
|
||||
},
|
||||
{
|
||||
id: "default-habbletcity",
|
||||
@@ -65,6 +136,32 @@ export const DEFAULT_SOURCES: CloneSource[] = [
|
||||
furnidataUrl: "https://www.habblet.city/gamedata/furnidata.json",
|
||||
nitroBaseUrl: "https://www.habblet.city/nitro/bundled/furniture",
|
||||
iconBaseUrl: "https://www.habblet.city/nitro/icons",
|
||||
hotel: "com",
|
||||
},
|
||||
{
|
||||
id: "default-habbo-retro",
|
||||
name: "Habbo Retro",
|
||||
furnidataUrl:
|
||||
"https://raw.githubusercontent.com/Izzxt/habbo-resource/master/gamedata/furnidata.json",
|
||||
nitroBaseUrl: "",
|
||||
iconBaseUrl: "",
|
||||
hotel: "com",
|
||||
},
|
||||
{
|
||||
id: "default-habbo-classic",
|
||||
name: "Habbo Classic",
|
||||
furnidataUrl: "https://www.habbo.com/gamedata/furnidata_json/1",
|
||||
nitroBaseUrl: "",
|
||||
iconBaseUrl: "",
|
||||
hotel: "com",
|
||||
},
|
||||
{
|
||||
id: "default-habbo-original",
|
||||
name: "Habbo Original",
|
||||
furnidataUrl: "https://habbo.com/gamedata/furnidata_json/1",
|
||||
nitroBaseUrl: "",
|
||||
iconBaseUrl: "",
|
||||
hotel: "com",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -96,6 +193,7 @@ export async function upsertSource(
|
||||
furnidataUrl: src.furnidataUrl.trim(),
|
||||
nitroBaseUrl: src.nitroBaseUrl.trim().replace(/\/+$/, ""),
|
||||
iconBaseUrl: src.iconBaseUrl.trim().replace(/\/+$/, ""),
|
||||
hotel: (src.hotel ?? "").trim(),
|
||||
};
|
||||
const idx = sources.findIndex((s) => s.id === entry.id);
|
||||
if (idx >= 0) sources[idx] = entry;
|
||||
|
||||
@@ -1,45 +1,68 @@
|
||||
import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { db, WebsiteBadges } from "@/lib/db";
|
||||
|
||||
const EXTERNAL_TEXTS_PATH = path.join(
|
||||
process.cwd(),
|
||||
"public/nitro-assets/gamedata/ExternalTexts.json",
|
||||
);
|
||||
import { getGamedataRoot } from "@/lib/services/furni-asset-dirs";
|
||||
|
||||
export type ImportBadgeResult =
|
||||
| { ok: true }
|
||||
| { ok: false; error: string; status: number };
|
||||
|
||||
function externalTextsPath(gamedataRoot: string): string {
|
||||
return path.join(gamedataRoot, "config", "ExternalTexts.json");
|
||||
}
|
||||
|
||||
/** Write badge name/desc into ExternalTexts.json (409 if already present). */
|
||||
export async function writeBadgeToExternalTexts(
|
||||
code: string,
|
||||
name: string,
|
||||
description: string,
|
||||
): Promise<ImportBadgeResult> {
|
||||
const gamedataRoot = await getGamedataRoot();
|
||||
if (!gamedataRoot) {
|
||||
return {
|
||||
ok: false,
|
||||
error: "gamedata_root not configured",
|
||||
status: 500,
|
||||
};
|
||||
}
|
||||
const extPath = externalTextsPath(gamedataRoot);
|
||||
const nameKey = `badge_name_${code}`;
|
||||
const descKey = `badge_desc_${code}`;
|
||||
|
||||
let texts: Record<string, string>;
|
||||
try {
|
||||
const raw = await fs.readFile(EXTERNAL_TEXTS_PATH, "utf-8");
|
||||
const texts = JSON.parse(raw) as Record<string, string>;
|
||||
|
||||
const nameKey = `badge_name_${code}`;
|
||||
const descKey = `badge_desc_${code}`;
|
||||
|
||||
if (texts[nameKey] !== undefined) {
|
||||
const raw = await fs.readFile(extPath, "utf-8");
|
||||
texts = JSON.parse(raw) as Record<string, string>;
|
||||
} catch (err) {
|
||||
// File not present yet (fresh deployment) — start with an empty object.
|
||||
if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
|
||||
console.warn(
|
||||
"[import-badges] Failed to update ExternalTexts.json:",
|
||||
(err as Error).message,
|
||||
);
|
||||
return {
|
||||
ok: false,
|
||||
error: "Badge already exists in ExternalTexts.json",
|
||||
status: 409,
|
||||
error: "Failed to update ExternalTexts.json",
|
||||
status: 500,
|
||||
};
|
||||
}
|
||||
texts = {};
|
||||
}
|
||||
|
||||
texts[nameKey] = name;
|
||||
texts[descKey] = description || "";
|
||||
if (texts[nameKey] !== undefined) {
|
||||
return {
|
||||
ok: false,
|
||||
error: "Badge already exists in ExternalTexts.json",
|
||||
status: 409,
|
||||
};
|
||||
}
|
||||
|
||||
await fs.writeFile(
|
||||
EXTERNAL_TEXTS_PATH,
|
||||
JSON.stringify(texts, null, 4),
|
||||
"utf-8",
|
||||
);
|
||||
texts[nameKey] = name;
|
||||
texts[descKey] = description || "";
|
||||
|
||||
try {
|
||||
await fs.mkdir(path.dirname(extPath), { recursive: true });
|
||||
await fs.writeFile(extPath, JSON.stringify(texts, null, 4), "utf-8");
|
||||
return { ok: true };
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
|
||||
@@ -62,6 +62,8 @@ vi.mock("@/lib/services/furni-import", () => ({
|
||||
async (insertFn: (nextId: number) => Promise<unknown>) => insertFn(701),
|
||||
),
|
||||
autoPriceFurni: vi.fn(() => ({ credits: 3, points: 0, pointsType: 0 })),
|
||||
CATEGORY_PAGE: {},
|
||||
classifyFurni: vi.fn(() => "other"),
|
||||
ensureDirectories: vi.fn(async () => {
|
||||
const dirs = [
|
||||
assetTargets.swfDir,
|
||||
@@ -76,6 +78,8 @@ vi.mock("@/lib/services/furni-import", () => ({
|
||||
await Promise.all(dirs.map((dir) => fs.mkdir(dir, { recursive: true })));
|
||||
}),
|
||||
getOrCreateCategoryPage: getOrCreateCategoryPageMock,
|
||||
IMPORTED_PAGE_CAPTION: "Imported Furniture",
|
||||
IMPORTED_PAGE_CAPTION_SAVE: "imported_furni",
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/services/swf/nitro-builder", () => ({
|
||||
|
||||
@@ -7,8 +7,12 @@ import { appendFurniEntry, buildFurniEntry } from "@/lib/services/furni-data";
|
||||
import {
|
||||
allocateCatalogItemId,
|
||||
autoPriceFurni,
|
||||
CATEGORY_PAGE,
|
||||
classifyFurni,
|
||||
ensureDirectories,
|
||||
getOrCreateCategoryPage,
|
||||
IMPORTED_PAGE_CAPTION,
|
||||
IMPORTED_PAGE_CAPTION_SAVE,
|
||||
} from "@/lib/services/furni-import";
|
||||
import { parseNitroBundle } from "@/lib/services/swf/nitro-builder";
|
||||
|
||||
@@ -153,6 +157,7 @@ async function writeSqlMigration(params: {
|
||||
interactionType: string;
|
||||
customparams: string;
|
||||
price: { credits: number; points: number; pointsType: number };
|
||||
categoryKey: string;
|
||||
}): Promise<string> {
|
||||
const num = await findNextMigrationNumber();
|
||||
const padded = String(num).padStart(4, "0");
|
||||
@@ -163,11 +168,29 @@ async function writeSqlMigration(params: {
|
||||
fileName,
|
||||
);
|
||||
|
||||
const catKey = params.categoryKey;
|
||||
const captionSave = `imp_${catKey}`;
|
||||
const catInfo = CATEGORY_PAGE[catKey] || CATEGORY_PAGE.other;
|
||||
|
||||
const lines: string[] = [
|
||||
`-- Migration: ${fileName}`,
|
||||
`-- Description: Import furniture "${params.classname}"`,
|
||||
`-- Generated: ${new Date().toISOString()}`,
|
||||
"",
|
||||
`-- Ensure the "Imported Furniture" parent page exists`,
|
||||
`INSERT INTO catalog_pages (caption_save, caption, page_layout, parent_id, min_rank, order_num, icon_image, enabled, visible, includes, page_headline, page_teaser, page_special, page_text1, page_text2, page_text_details, page_text_teaser)`,
|
||||
`SELECT ${escapeSql(IMPORTED_PAGE_CAPTION_SAVE)}, ${escapeSql(IMPORTED_PAGE_CAPTION)}, 'default_3x3', -1, '1', 9999, 263, '1', '1', '', '', '', '', '', '', '', ''`,
|
||||
`WHERE NOT EXISTS (SELECT 1 FROM catalog_pages WHERE caption_save = ${escapeSql(IMPORTED_PAGE_CAPTION_SAVE)});`,
|
||||
"",
|
||||
`-- Ensure the category sub-page exists (${catKey})`,
|
||||
`INSERT INTO catalog_pages (caption_save, caption, page_layout, parent_id, min_rank, order_num, icon_image, enabled, visible, includes, page_headline, page_teaser, page_special, page_text1, page_text2, page_text_details, page_text_teaser)`,
|
||||
`SELECT ${escapeSql(captionSave)}, ${escapeSql(catInfo.label)}, 'default_3x3', p.id, '1', ${catInfo.order}, ${catInfo.icon}, '1', '1', '', '', '', '', '', '', '', ''`,
|
||||
`FROM catalog_pages p`,
|
||||
`WHERE p.caption_save = ${escapeSql(IMPORTED_PAGE_CAPTION_SAVE)}`,
|
||||
` AND NOT EXISTS (SELECT 1 FROM catalog_pages c WHERE c.caption_save = ${escapeSql(captionSave)} AND c.parent_id = p.id);`,
|
||||
"",
|
||||
`SET @next_id = (SELECT COALESCE(MAX(id), 0) + 1 FROM items_base);`,
|
||||
"",
|
||||
`INSERT INTO items_base`,
|
||||
` (id, sprite_id, public_name, item_name, type, width, length, stack_height,`,
|
||||
` allow_stack, allow_sit, allow_lay, allow_walk, interaction_type, customparams)`,
|
||||
@@ -177,13 +200,12 @@ async function writeSqlMigration(params: {
|
||||
` ${params.allowStack}, ${params.cansiton ? "1" : "0"}, ${params.canlayon ? "1" : "0"}, ${params.canstandon ? "1" : "0"},`,
|
||||
` ${escapeSql(params.interactionType)}, ${escapeSql(params.customparams)});`,
|
||||
"",
|
||||
`SET @next_id = LAST_INSERT_ID();`,
|
||||
"",
|
||||
`INSERT INTO catalog_items`,
|
||||
` (id, page_id, item_ids, catalog_name, cost_credits, cost_points, points_type, amount, order_number, offer_id, extradata)`,
|
||||
`VALUES`,
|
||||
` (NULL, ${escapeSql("imp_other")}, @next_id, ${escapeSql(params.classname)},`,
|
||||
` ${params.price.credits}, ${params.price.points}, ${params.price.pointsType}, 1, 1, '-1', '');`,
|
||||
`SELECT (SELECT COALESCE(MAX(id), 0) + 1 FROM catalog_items), p.id, @next_id, ${escapeSql(params.classname)},`,
|
||||
` ${params.price.credits}, ${params.price.points}, ${params.price.pointsType}, 1, 1, '-1', ''`,
|
||||
`FROM catalog_pages p`,
|
||||
`WHERE p.caption_save = ${escapeSql(captionSave)};`,
|
||||
"",
|
||||
];
|
||||
|
||||
@@ -379,6 +401,7 @@ export async function uploadSingleFurni(params: {
|
||||
interactionType: userInteractionType,
|
||||
customparams,
|
||||
price,
|
||||
categoryKey: classifyFurni(classname, itemType),
|
||||
});
|
||||
warnings.push(`SQL migration written: ${sqlFile}`);
|
||||
} catch (err) {
|
||||
|
||||
Reference in new issue
Block a user