This commit is contained in:
1 parent
8efd032cc6
commit
df38dccbf1
735 files changed
+128321
-120870
No files matched your search
+108
-105
@@ -10,14 +10,15 @@ export type { HabboAssetBadge } from "@/types/furni";
|
||||
|
||||
import type { HabboAssetBadge } from "@/types/furni";
|
||||
|
||||
const EXTERNAL_TEXTS_URL = "https://www.habbo.it/gamedata/external_flash_texts/1";
|
||||
const EXTERNAL_TEXTS_URL =
|
||||
"https://www.habbo.it/gamedata/external_flash_texts/1";
|
||||
const BADGE_IMAGE_BASE = "https://images.habbo.com/c_images/album1584";
|
||||
const CACHE_TTL = 30 * 60 * 1000; // 30 minutes
|
||||
|
||||
interface BadgeEntry {
|
||||
code: string;
|
||||
name: string;
|
||||
description: string;
|
||||
code: string;
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
let badgeCache: BadgeEntry[] | null = null;
|
||||
@@ -29,90 +30,92 @@ let loadingPromise: Promise<BadgeEntry[]> | null = null;
|
||||
* Cached in memory for 30 minutes.
|
||||
*/
|
||||
async function loadBadges(): Promise<BadgeEntry[]> {
|
||||
const now = Date.now();
|
||||
if (badgeCache && now - cacheTimestamp < CACHE_TTL) return badgeCache;
|
||||
if (loadingPromise) return loadingPromise;
|
||||
const now = Date.now();
|
||||
if (badgeCache && now - cacheTimestamp < CACHE_TTL) return badgeCache;
|
||||
if (loadingPromise) return loadingPromise;
|
||||
|
||||
loadingPromise = (async () => {
|
||||
try {
|
||||
if (!EXTERNAL_TEXTS_URL) {
|
||||
console.warn("[badges] HABBO_EXTERNAL_TEXTS_URL not configured");
|
||||
return badgeCache || [];
|
||||
}
|
||||
loadingPromise = (async () => {
|
||||
try {
|
||||
if (!EXTERNAL_TEXTS_URL) {
|
||||
console.warn("[badges] HABBO_EXTERNAL_TEXTS_URL not configured");
|
||||
return badgeCache || [];
|
||||
}
|
||||
|
||||
const res = await fetch(EXTERNAL_TEXTS_URL, {
|
||||
signal: AbortSignal.timeout(20000),
|
||||
});
|
||||
const res = await fetch(EXTERNAL_TEXTS_URL, {
|
||||
signal: AbortSignal.timeout(20000),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
console.warn(`[badges] Fetch failed: ${res.status}`);
|
||||
return badgeCache || [];
|
||||
}
|
||||
if (!res.ok) {
|
||||
console.warn(`[badges] Fetch failed: ${res.status}`);
|
||||
return badgeCache || [];
|
||||
}
|
||||
|
||||
const text = await res.text();
|
||||
const lines = text.split("\n");
|
||||
const text = await res.text();
|
||||
const lines = text.split("\n");
|
||||
|
||||
// Parse key=value format, extract badge_name_CODE and badge_desc_CODE
|
||||
const nameMap = new Map<string, string>();
|
||||
const descMap = new Map<string, string>();
|
||||
// Parse key=value format, extract badge_name_CODE and badge_desc_CODE
|
||||
const nameMap = new Map<string, string>();
|
||||
const descMap = new Map<string, string>();
|
||||
|
||||
for (const line of lines) {
|
||||
const eqIdx = line.indexOf("=");
|
||||
if (eqIdx === -1) continue;
|
||||
const key = line.substring(0, eqIdx).trim();
|
||||
const value = line.substring(eqIdx + 1).trim();
|
||||
for (const line of lines) {
|
||||
const eqIdx = line.indexOf("=");
|
||||
if (eqIdx === -1) continue;
|
||||
const key = line.substring(0, eqIdx).trim();
|
||||
const value = line.substring(eqIdx + 1).trim();
|
||||
|
||||
// Format: badge_name_CODE or CODE_badge_name
|
||||
let match = key.match(/^badge_name_(.+)$/);
|
||||
if (match) {
|
||||
nameMap.set(match[1], value);
|
||||
continue;
|
||||
}
|
||||
match = key.match(/^(.+)_badge_name$/);
|
||||
if (match) {
|
||||
nameMap.set(match[1], value);
|
||||
continue;
|
||||
}
|
||||
// Format: badge_name_CODE or CODE_badge_name
|
||||
let match = key.match(/^badge_name_(.+)$/);
|
||||
if (match) {
|
||||
nameMap.set(match[1], value);
|
||||
continue;
|
||||
}
|
||||
match = key.match(/^(.+)_badge_name$/);
|
||||
if (match) {
|
||||
nameMap.set(match[1], value);
|
||||
continue;
|
||||
}
|
||||
|
||||
match = key.match(/^badge_desc_(.+)$/);
|
||||
if (match) {
|
||||
descMap.set(match[1], value);
|
||||
continue;
|
||||
}
|
||||
match = key.match(/^(.+)_badge_desc$/);
|
||||
if (match) {
|
||||
descMap.set(match[1], value);
|
||||
}
|
||||
}
|
||||
match = key.match(/^badge_desc_(.+)$/);
|
||||
if (match) {
|
||||
descMap.set(match[1], value);
|
||||
continue;
|
||||
}
|
||||
match = key.match(/^(.+)_badge_desc$/);
|
||||
if (match) {
|
||||
descMap.set(match[1], value);
|
||||
}
|
||||
}
|
||||
|
||||
// Merge into badge entries
|
||||
const allCodes = new Set([...nameMap.keys(), ...descMap.keys()]);
|
||||
const badges: BadgeEntry[] = [];
|
||||
// Merge into badge entries
|
||||
const allCodes = new Set([...nameMap.keys(), ...descMap.keys()]);
|
||||
const badges: BadgeEntry[] = [];
|
||||
|
||||
for (const code of allCodes) {
|
||||
badges.push({
|
||||
code,
|
||||
name: nameMap.get(code) || "",
|
||||
description: descMap.get(code) || "",
|
||||
});
|
||||
}
|
||||
for (const code of allCodes) {
|
||||
badges.push({
|
||||
code,
|
||||
name: nameMap.get(code) || "",
|
||||
description: descMap.get(code) || "",
|
||||
});
|
||||
}
|
||||
|
||||
// Sort alphabetically by code
|
||||
badges.sort((a, b) => a.code.localeCompare(b.code));
|
||||
// Sort alphabetically by code
|
||||
badges.sort((a, b) => a.code.localeCompare(b.code));
|
||||
|
||||
console.debug(`[badges] Loaded ${badges.length} badges from external texts`);
|
||||
badgeCache = badges;
|
||||
cacheTimestamp = now;
|
||||
return badges;
|
||||
} catch (err) {
|
||||
console.warn("[badges] Load error:", (err as Error).message);
|
||||
return badgeCache || [];
|
||||
} finally {
|
||||
loadingPromise = null;
|
||||
}
|
||||
})();
|
||||
console.debug(
|
||||
`[badges] Loaded ${badges.length} badges from external texts`,
|
||||
);
|
||||
badgeCache = badges;
|
||||
cacheTimestamp = now;
|
||||
return badges;
|
||||
} catch (err) {
|
||||
console.warn("[badges] Load error:", (err as Error).message);
|
||||
return badgeCache || [];
|
||||
} finally {
|
||||
loadingPromise = null;
|
||||
}
|
||||
})();
|
||||
|
||||
return loadingPromise;
|
||||
return loadingPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,42 +123,42 @@ async function loadBadges(): Promise<BadgeEntry[]> {
|
||||
* Returns badges with image URLs from images.habbo.com.
|
||||
*/
|
||||
export async function searchBadges(
|
||||
term: string,
|
||||
limit = 100,
|
||||
offset = 0,
|
||||
_hotel: string | null = null, // kept for API compat, ignored (single source)
|
||||
term: string,
|
||||
limit = 100,
|
||||
offset = 0,
|
||||
_hotel: string | null = null, // kept for API compat, ignored (single source)
|
||||
): Promise<HabboAssetBadge[]> {
|
||||
const badges = await loadBadges();
|
||||
const search = term.toLowerCase().trim();
|
||||
const badges = await loadBadges();
|
||||
const search = term.toLowerCase().trim();
|
||||
|
||||
let filtered = badges;
|
||||
if (search) {
|
||||
filtered = badges.filter(
|
||||
(b) =>
|
||||
b.code.toLowerCase().includes(search) ||
|
||||
b.name.toLowerCase().includes(search) ||
|
||||
b.description.toLowerCase().includes(search),
|
||||
);
|
||||
} else {
|
||||
// No search term: return newest (reverse sorted, last entries are newest)
|
||||
filtered = [...badges].reverse();
|
||||
}
|
||||
let filtered = badges;
|
||||
if (search) {
|
||||
filtered = badges.filter(
|
||||
(b) =>
|
||||
b.code.toLowerCase().includes(search) ||
|
||||
b.name.toLowerCase().includes(search) ||
|
||||
b.description.toLowerCase().includes(search),
|
||||
);
|
||||
} else {
|
||||
// No search term: return newest (reverse sorted, last entries are newest)
|
||||
filtered = [...badges].reverse();
|
||||
}
|
||||
|
||||
const page = filtered.slice(offset, offset + limit);
|
||||
const page = filtered.slice(offset, offset + limit);
|
||||
|
||||
return page.map((b) => ({
|
||||
id: 0,
|
||||
code: b.code,
|
||||
name: b.name,
|
||||
description: b.description,
|
||||
hotel: "it",
|
||||
url_habbo: `${BADGE_IMAGE_BASE}/${b.code}.gif`,
|
||||
url_habboassets: `${BADGE_IMAGE_BASE}/${b.code}.gif`, // same source, no fallback needed
|
||||
}));
|
||||
return page.map((b) => ({
|
||||
id: 0,
|
||||
code: b.code,
|
||||
name: b.name,
|
||||
description: b.description,
|
||||
hotel: "it",
|
||||
url_habbo: `${BADGE_IMAGE_BASE}/${b.code}.gif`,
|
||||
url_habboassets: `${BADGE_IMAGE_BASE}/${b.code}.gif`, // same source, no fallback needed
|
||||
}));
|
||||
}
|
||||
|
||||
/** Clear the badge cache */
|
||||
export function clearBadgeCache(): void {
|
||||
badgeCache = null;
|
||||
cacheTimestamp = 0;
|
||||
badgeCache = null;
|
||||
cacheTimestamp = 0;
|
||||
}
|
||||
Reference in new issue
Block a user