fix: badge import uses configured gamedata root for ExternalTexts.json
CI / check (push) Successful in 30s
CI / release (push) Skipped
CI / deploy (push) Successful in 44s

- import-badge.ts, badges route, and badges edit route now resolve
  ExternalTexts.json via getGamedataRoot() instead of the hardcoded
  public/nitro-assets/gamedata/ path
- writeBadgeToExternalTexts creates the file (and parent dirs) when
  missing, so fresh deployments no longer fail with ENOENT
- upload-import SQL maker now classifies furni via classifyFurni and
  generates correct category sub-pages (imp_<catKey>) instead of
  hardcoded imp_other
This commit is contained in:
openhands committed 2026-08-05 15:34:40 +02:00
1 parent 4d4cbd2211
commit 79b82e0a31
4 files changed
+439 -26

No files matched your search

@@ -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": ""
}
@@ -3,11 +3,11 @@ import path from "node:path";
import { withAdmin } from "@/lib/api-handler"; import { withAdmin } from "@/lib/api-handler";
import { apiError, apiOk } from "@/lib/api-response"; import { apiError, apiOk } from "@/lib/api-response";
import { PERMS } from "@/lib/permissions"; import { PERMS } from "@/lib/permissions";
import { getGamedataRoot } from "@/lib/services/furni-asset-dirs";
const EXTERNAL_TEXTS_PATH = path.join( function externalTextsPath(gamedataRoot: string): string {
process.cwd(), return path.join(gamedataRoot, "config", "ExternalTexts.json");
"public/nitro-assets/gamedata/ExternalTexts.json", }
);
/** GET - Read badge name/desc from ExternalTexts.json */ /** GET - Read badge name/desc from ExternalTexts.json */
export const GET = withAdmin( export const GET = withAdmin(
@@ -16,8 +16,11 @@ export const GET = withAdmin(
const code = request.nextUrl.searchParams.get("code")?.toUpperCase(); const code = request.nextUrl.searchParams.get("code")?.toUpperCase();
if (!code) return apiError("Missing code", 400); if (!code) return apiError("Missing code", 400);
const gamedataRoot = await getGamedataRoot();
if (!gamedataRoot) return apiError("gamedata_root not configured", 500);
try { 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 texts: Record<string, string> = JSON.parse(raw);
const name = const name =
+12 -9
View File
@@ -4,13 +4,13 @@ import { withAdmin } from "@/lib/api-handler";
import { apiError, apiOk } from "@/lib/api-response"; import { apiError, apiOk } from "@/lib/api-response";
import { PERMS } from "@/lib/permissions"; import { PERMS } from "@/lib/permissions";
import { logAudit } from "@/lib/services/audit"; import { logAudit } from "@/lib/services/audit";
import { getGamedataRoot } from "@/lib/services/furni-asset-dirs";
import { searchBadges } from "@/lib/services/habboassets"; import { searchBadges } from "@/lib/services/habboassets";
import { importBadgeSynced } from "@/lib/services/import-badge"; import { importBadgeSynced } from "@/lib/services/import-badge";
const EXTERNAL_TEXTS_PATH = path.join( function externalTextsPath(gamedataRoot: string): string {
process.cwd(), return path.join(gamedataRoot, "config", "ExternalTexts.json");
"public/nitro-assets/gamedata/ExternalTexts.json", }
);
// ── Search badges via official Habbo external texts ──────────────── // ── Search badges via official Habbo external texts ────────────────
export const GET = withAdmin( export const GET = withAdmin(
@@ -33,11 +33,14 @@ export const GET = withAdmin(
// Check which badges already exist in ExternalTexts.json // Check which badges already exist in ExternalTexts.json
const existingCodes = new Set<string>(); const existingCodes = new Set<string>();
try { try {
const raw = await fs.readFile(EXTERNAL_TEXTS_PATH, "utf-8"); const gamedataRoot = await getGamedataRoot();
const texts = JSON.parse(raw); if (gamedataRoot) {
for (const badge of badges) { const raw = await fs.readFile(externalTextsPath(gamedataRoot), "utf-8");
if (texts[`badge_name_${badge.code}`] !== undefined) { const texts = JSON.parse(raw);
existingCodes.add(badge.code); for (const badge of badges) {
if (texts[`badge_name_${badge.code}`] !== undefined) {
existingCodes.add(badge.code);
}
} }
} }
} catch { } catch {
+17 -12
View File
@@ -1,28 +1,37 @@
import { promises as fs } from "node:fs"; import { promises as fs } from "node:fs";
import path from "node:path"; import path from "node:path";
import { db, WebsiteBadges } from "@/lib/db"; import { db, WebsiteBadges } from "@/lib/db";
import { getGamedataRoot } from "@/lib/services/furni-asset-dirs";
const EXTERNAL_TEXTS_PATH = path.join(
process.cwd(),
"public/nitro-assets/gamedata/ExternalTexts.json",
);
export type ImportBadgeResult = export type ImportBadgeResult =
| { ok: true } | { ok: true }
| { ok: false; error: string; status: number }; | { 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). */ /** Write badge name/desc into ExternalTexts.json (409 if already present). */
export async function writeBadgeToExternalTexts( export async function writeBadgeToExternalTexts(
code: string, code: string,
name: string, name: string,
description: string, description: string,
): Promise<ImportBadgeResult> { ): 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 nameKey = `badge_name_${code}`;
const descKey = `badge_desc_${code}`; const descKey = `badge_desc_${code}`;
let texts: Record<string, string>; let texts: Record<string, string>;
try { try {
const raw = await fs.readFile(EXTERNAL_TEXTS_PATH, "utf-8"); const raw = await fs.readFile(extPath, "utf-8");
texts = JSON.parse(raw) as Record<string, string>; texts = JSON.parse(raw) as Record<string, string>;
} catch (err) { } catch (err) {
// File not present yet (fresh deployment) — start with an empty object. // File not present yet (fresh deployment) — start with an empty object.
@@ -52,12 +61,8 @@ export async function writeBadgeToExternalTexts(
texts[descKey] = description || ""; texts[descKey] = description || "";
try { try {
await fs.mkdir(path.dirname(EXTERNAL_TEXTS_PATH), { recursive: true }); await fs.mkdir(path.dirname(extPath), { recursive: true });
await fs.writeFile( await fs.writeFile(extPath, JSON.stringify(texts, null, 4), "utf-8");
EXTERNAL_TEXTS_PATH,
JSON.stringify(texts, null, 4),
"utf-8",
);
return { ok: true }; return { ok: true };
} catch (err) { } catch (err) {
console.warn( console.warn(