howlongtobeat-core 0.1.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 (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +157 -0
  3. package/esm/mod.d.ts +30 -0
  4. package/esm/mod.d.ts.map +1 -0
  5. package/esm/mod.js +31 -0
  6. package/esm/package.json +3 -0
  7. package/esm/src/HowLongToBeat.d.ts +63 -0
  8. package/esm/src/HowLongToBeat.d.ts.map +1 -0
  9. package/esm/src/HowLongToBeat.js +142 -0
  10. package/esm/src/http/client.d.ts +29 -0
  11. package/esm/src/http/client.d.ts.map +1 -0
  12. package/esm/src/http/client.js +250 -0
  13. package/esm/src/parser/json.d.ts +17 -0
  14. package/esm/src/parser/json.d.ts.map +1 -0
  15. package/esm/src/parser/json.js +113 -0
  16. package/esm/src/types.d.ts +179 -0
  17. package/esm/src/types.d.ts.map +1 -0
  18. package/esm/src/types.js +19 -0
  19. package/esm/src/utils/similarity.d.ts +28 -0
  20. package/esm/src/utils/similarity.d.ts.map +1 -0
  21. package/esm/src/utils/similarity.js +127 -0
  22. package/package.json +38 -0
  23. package/script/mod.d.ts +30 -0
  24. package/script/mod.d.ts.map +1 -0
  25. package/script/mod.js +39 -0
  26. package/script/package.json +3 -0
  27. package/script/src/HowLongToBeat.d.ts +63 -0
  28. package/script/src/HowLongToBeat.d.ts.map +1 -0
  29. package/script/src/HowLongToBeat.js +146 -0
  30. package/script/src/http/client.d.ts +29 -0
  31. package/script/src/http/client.d.ts.map +1 -0
  32. package/script/src/http/client.js +258 -0
  33. package/script/src/parser/json.d.ts +17 -0
  34. package/script/src/parser/json.d.ts.map +1 -0
  35. package/script/src/parser/json.js +118 -0
  36. package/script/src/types.d.ts +179 -0
  37. package/script/src/types.d.ts.map +1 -0
  38. package/script/src/types.js +22 -0
  39. package/script/src/utils/similarity.d.ts +28 -0
  40. package/script/src/utils/similarity.d.ts.map +1 -0
  41. package/script/src/utils/similarity.js +133 -0
@@ -0,0 +1,250 @@
1
+ /**
2
+ * HTTP client for HowLongToBeat API
3
+ */
4
+ const HLTB_BASE_URL = "https://howlongtobeat.com";
5
+ const HLTB_SEARCH_URL = `${HLTB_BASE_URL}/api/search`;
6
+ const HLTB_GAME_URL = `${HLTB_BASE_URL}/game`;
7
+ /**
8
+ * List of User-Agent strings for rotation
9
+ */
10
+ const USER_AGENTS = [
11
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
12
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
13
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0",
14
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15",
15
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
16
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0",
17
+ ];
18
+ /**
19
+ * Get a random User-Agent string
20
+ */
21
+ function getRandomUserAgent() {
22
+ return USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)];
23
+ }
24
+ /**
25
+ * Cached auth token and its expiry
26
+ */
27
+ let cachedToken = null;
28
+ let tokenExpiry = 0;
29
+ const TOKEN_TTL_MS = 5 * 60 * 1000; // 5 minutes
30
+ /**
31
+ * Cached search API URL
32
+ */
33
+ let cachedSearchUrl = null;
34
+ /**
35
+ * Fetch auth token from HLTB
36
+ */
37
+ async function fetchAuthToken() {
38
+ // Check cache first
39
+ if (cachedToken && Date.now() < tokenExpiry) {
40
+ return cachedToken;
41
+ }
42
+ try {
43
+ const timestamp = Date.now();
44
+ const response = await fetch(`${HLTB_BASE_URL}/api/search/init?t=${timestamp}`, {
45
+ headers: {
46
+ "User-Agent": getRandomUserAgent(),
47
+ "Referer": HLTB_BASE_URL,
48
+ },
49
+ });
50
+ if (!response.ok) {
51
+ // TODO: implement proper error handling
52
+ console.error(`Failed to fetch auth token: ${response.status}`);
53
+ return null;
54
+ }
55
+ const data = (await response.json());
56
+ cachedToken = data.token || null;
57
+ tokenExpiry = Date.now() + TOKEN_TTL_MS;
58
+ return cachedToken;
59
+ }
60
+ catch (error) {
61
+ // TODO: implement proper error handling
62
+ console.error("Error fetching auth token:", error);
63
+ return null;
64
+ }
65
+ }
66
+ /**
67
+ * Discover the dynamic search API URL from HLTB's JavaScript bundles
68
+ */
69
+ async function discoverSearchUrl() {
70
+ if (cachedSearchUrl) {
71
+ return cachedSearchUrl;
72
+ }
73
+ try {
74
+ // Fetch the main page
75
+ const response = await fetch(HLTB_BASE_URL, {
76
+ headers: {
77
+ "User-Agent": getRandomUserAgent(),
78
+ },
79
+ });
80
+ if (!response.ok) {
81
+ // TODO: implement proper error handling
82
+ console.error(`Failed to fetch main page: ${response.status}`);
83
+ return HLTB_SEARCH_URL;
84
+ }
85
+ const html = await response.text();
86
+ // Find _app-*.js bundle URLs
87
+ const scriptPattern = /_app-[a-zA-Z0-9]+\.js/g;
88
+ const scripts = html.match(scriptPattern);
89
+ if (!scripts || scripts.length === 0) {
90
+ return HLTB_SEARCH_URL;
91
+ }
92
+ // Try to find the search endpoint in the scripts
93
+ for (const script of scripts) {
94
+ try {
95
+ const scriptUrl = `${HLTB_BASE_URL}/_next/static/chunks/pages/${script}`;
96
+ const scriptResponse = await fetch(scriptUrl, {
97
+ headers: {
98
+ "User-Agent": getRandomUserAgent(),
99
+ },
100
+ });
101
+ if (!scriptResponse.ok)
102
+ continue;
103
+ const scriptContent = await scriptResponse.text();
104
+ // Look for the search API path pattern
105
+ // Common patterns: "/api/search/", "api/search", etc.
106
+ const searchPathPattern = /["']([^"']*api\/search[^"']*)["']/g;
107
+ const matches = [...scriptContent.matchAll(searchPathPattern)];
108
+ for (const match of matches) {
109
+ const path = match[1];
110
+ if (path && !path.includes("init") && path.startsWith("/")) {
111
+ cachedSearchUrl = `${HLTB_BASE_URL}${path}`;
112
+ return cachedSearchUrl;
113
+ }
114
+ }
115
+ }
116
+ catch {
117
+ // TODO: implement proper error handling
118
+ continue;
119
+ }
120
+ }
121
+ }
122
+ catch (error) {
123
+ // TODO: implement proper error handling
124
+ console.error("Error discovering search URL:", error);
125
+ }
126
+ // Fall back to known URL
127
+ cachedSearchUrl = HLTB_SEARCH_URL;
128
+ return cachedSearchUrl;
129
+ }
130
+ /**
131
+ * Build search request body
132
+ */
133
+ export function buildSearchRequest(searchTerms, modifier = "", page = 1, size = 20) {
134
+ return {
135
+ searchType: "games",
136
+ searchTerms,
137
+ searchPage: page,
138
+ size,
139
+ searchOptions: {
140
+ games: {
141
+ userId: 0,
142
+ platform: "",
143
+ sortCategory: "popular",
144
+ rangeCategory: "main",
145
+ rangeTime: { min: 0, max: 0 },
146
+ gameplay: {
147
+ perspective: "",
148
+ flow: "",
149
+ genre: "",
150
+ difficulty: "",
151
+ },
152
+ rangeYear: { max: "", min: "" },
153
+ modifier,
154
+ },
155
+ users: { sortCategory: "postcount" },
156
+ lists: { sortCategory: "follows" },
157
+ filter: "",
158
+ sort: 0,
159
+ randomizer: 0,
160
+ },
161
+ useCache: true,
162
+ };
163
+ }
164
+ /**
165
+ * Execute a search request to HLTB API
166
+ */
167
+ export async function executeSearch(gameName, modifier = "") {
168
+ const token = await fetchAuthToken();
169
+ if (!token) {
170
+ // TODO: implement proper error handling
171
+ return null;
172
+ }
173
+ const searchUrl = await discoverSearchUrl();
174
+ const searchTerms = gameName.split(" ").filter((term) => term.length > 0);
175
+ const requestBody = buildSearchRequest(searchTerms, modifier);
176
+ try {
177
+ const response = await fetch(searchUrl, {
178
+ method: "POST",
179
+ headers: {
180
+ "Content-Type": "application/json",
181
+ "User-Agent": getRandomUserAgent(),
182
+ "Referer": HLTB_BASE_URL,
183
+ "x-auth-token": token,
184
+ },
185
+ body: JSON.stringify(requestBody),
186
+ });
187
+ if (!response.ok) {
188
+ // TODO: implement proper error handling
189
+ console.error(`Search request failed: ${response.status}`);
190
+ return null;
191
+ }
192
+ return await response.json();
193
+ }
194
+ catch (error) {
195
+ // TODO: implement proper error handling
196
+ console.error("Error executing search:", error);
197
+ return null;
198
+ }
199
+ }
200
+ /**
201
+ * Fetch game page HTML to extract game title
202
+ */
203
+ export async function fetchGamePage(gameId) {
204
+ try {
205
+ const response = await fetch(`${HLTB_GAME_URL}/${gameId}`, {
206
+ headers: {
207
+ "User-Agent": getRandomUserAgent(),
208
+ "Referer": HLTB_BASE_URL,
209
+ },
210
+ });
211
+ if (!response.ok) {
212
+ // TODO: implement proper error handling
213
+ return null;
214
+ }
215
+ return await response.text();
216
+ }
217
+ catch (error) {
218
+ // TODO: implement proper error handling
219
+ console.error("Error fetching game page:", error);
220
+ return null;
221
+ }
222
+ }
223
+ /**
224
+ * Extract game title from HLTB game page HTML
225
+ */
226
+ export function extractGameTitle(html) {
227
+ // Title format: "How long is {GAME_NAME}? | HowLongToBeat"
228
+ // Note: title tag may have attributes like data-next-head=""
229
+ const titlePattern = /<title[^>]*>How long is ([^?]+)\?[^<]*<\/title>/i;
230
+ const match = html.match(titlePattern);
231
+ if (match && match[1]) {
232
+ return match[1].trim();
233
+ }
234
+ // TODO: implement proper error handling
235
+ return null;
236
+ }
237
+ /**
238
+ * Get base URL for constructing image and web links
239
+ */
240
+ export function getBaseUrl() {
241
+ return HLTB_BASE_URL;
242
+ }
243
+ /**
244
+ * Clear cached token and search URL (useful for testing)
245
+ */
246
+ export function clearCache() {
247
+ cachedToken = null;
248
+ tokenExpiry = 0;
249
+ cachedSearchUrl = null;
250
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * JSON response parser for HowLongToBeat API
3
+ */
4
+ import type { HLTBRawGame, HowLongToBeatEntry } from "../types.js";
5
+ /**
6
+ * Convert a raw HLTB game object to HowLongToBeatEntry
7
+ */
8
+ export declare function parseGameEntry(raw: HLTBRawGame, similarity?: number, autoFilterTimes?: boolean): HowLongToBeatEntry;
9
+ /**
10
+ * Parse an array of raw games into HowLongToBeatEntry array
11
+ */
12
+ export declare function parseGameEntries(rawGames: HLTBRawGame[], searchQuery: string, autoFilterTimes: boolean, calculateSimilarity: (a: string, b: string) => number): HowLongToBeatEntry[];
13
+ /**
14
+ * Filter entries by minimum similarity threshold
15
+ */
16
+ export declare function filterBySimilarity(entries: HowLongToBeatEntry[], minimumSimilarity: number): HowLongToBeatEntry[];
17
+ //# sourceMappingURL=json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../../src/src/parser/json.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AA2BnE;;GAEG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,WAAW,EAChB,UAAU,GAAE,MAAU,EACtB,eAAe,GAAE,OAAe,GAC/B,kBAAkB,CAyEpB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,WAAW,EAAE,EACvB,WAAW,EAAE,MAAM,EACnB,eAAe,EAAE,OAAO,EACxB,mBAAmB,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,MAAM,GACpD,kBAAkB,EAAE,CAgBtB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,kBAAkB,EAAE,EAC7B,iBAAiB,EAAE,MAAM,GACxB,kBAAkB,EAAE,CAItB"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * JSON response parser for HowLongToBeat API
3
+ */
4
+ import { getBaseUrl } from "../http/client.js";
5
+ const HLTB_IMAGE_URL = "https://howlongtobeat.com/games";
6
+ /**
7
+ * Convert seconds to hours with one decimal place
8
+ */
9
+ function secondsToHours(seconds) {
10
+ if (seconds <= 0) {
11
+ return null;
12
+ }
13
+ return Math.round((seconds / 3600) * 10) / 10;
14
+ }
15
+ /**
16
+ * Parse platforms string into array
17
+ */
18
+ function parsePlatforms(platformString) {
19
+ if (!platformString || platformString.trim() === "") {
20
+ return null;
21
+ }
22
+ return platformString.split(",").map((p) => p.trim()).filter((p) => p.length > 0);
23
+ }
24
+ /**
25
+ * Convert a raw HLTB game object to HowLongToBeatEntry
26
+ */
27
+ export function parseGameEntry(raw, similarity = 0, autoFilterTimes = false) {
28
+ const baseUrl = getBaseUrl();
29
+ // Determine game type flags
30
+ const complexityLvlCombine = raw.comp_lvl_combine === 1;
31
+ const complexityLvlSp = raw.comp_lvl_sp === 1;
32
+ const complexityLvlCo = raw.comp_lvl_co === 1;
33
+ const complexityLvlMp = raw.comp_lvl_mp === 1;
34
+ // Parse times
35
+ let mainStory = secondsToHours(raw.comp_main);
36
+ let mainExtra = secondsToHours(raw.comp_plus);
37
+ let completionist = secondsToHours(raw.comp_100);
38
+ let allStyles = secondsToHours(raw.comp_all);
39
+ let coopTime = secondsToHours(raw.invested_co);
40
+ let mpTime = secondsToHours(raw.invested_mp);
41
+ // Auto-filter times based on game type
42
+ if (autoFilterTimes) {
43
+ // If no singleplayer, nullify SP times
44
+ if (!complexityLvlSp && !complexityLvlCombine) {
45
+ mainStory = null;
46
+ mainExtra = null;
47
+ completionist = null;
48
+ allStyles = null;
49
+ }
50
+ // If no co-op, nullify co-op time
51
+ if (!complexityLvlCo && !complexityLvlCombine) {
52
+ coopTime = null;
53
+ }
54
+ // If no multiplayer, nullify MP time
55
+ if (!complexityLvlMp && !complexityLvlCombine) {
56
+ mpTime = null;
57
+ }
58
+ }
59
+ // Build image URL
60
+ let gameImageUrl = null;
61
+ if (raw.game_image) {
62
+ gameImageUrl = `${HLTB_IMAGE_URL}/${raw.game_image}`;
63
+ }
64
+ return {
65
+ gameId: raw.game_id,
66
+ gameName: raw.game_name || null,
67
+ gameAlias: raw.game_alias || null,
68
+ gameType: raw.game_type || null,
69
+ gameImageUrl,
70
+ gameWebLink: `${baseUrl}/game/${raw.game_id}`,
71
+ reviewScore: raw.review_score > 0 ? raw.review_score : null,
72
+ profileDev: raw.profile_dev || null,
73
+ profilePlatforms: parsePlatforms(raw.profile_platform),
74
+ releaseWorld: raw.release_world > 0 ? raw.release_world : null,
75
+ mainStory,
76
+ mainExtra,
77
+ completionist,
78
+ allStyles,
79
+ coopTime,
80
+ mpTime,
81
+ complexityLvlCombine,
82
+ complexityLvlSp,
83
+ complexityLvlCo,
84
+ complexityLvlMp,
85
+ similarity,
86
+ jsonContent: raw,
87
+ };
88
+ }
89
+ /**
90
+ * Parse an array of raw games into HowLongToBeatEntry array
91
+ */
92
+ export function parseGameEntries(rawGames, searchQuery, autoFilterTimes, calculateSimilarity) {
93
+ const normalizedQuery = searchQuery.toLowerCase().trim();
94
+ return rawGames.map((raw) => {
95
+ // Calculate similarity against game name and alias
96
+ const nameSimilarity = raw.game_name
97
+ ? calculateSimilarity(normalizedQuery, raw.game_name.toLowerCase())
98
+ : 0;
99
+ const aliasSimilarity = raw.game_alias
100
+ ? calculateSimilarity(normalizedQuery, raw.game_alias.toLowerCase())
101
+ : 0;
102
+ const similarity = Math.max(nameSimilarity, aliasSimilarity);
103
+ return parseGameEntry(raw, similarity, autoFilterTimes);
104
+ });
105
+ }
106
+ /**
107
+ * Filter entries by minimum similarity threshold
108
+ */
109
+ export function filterBySimilarity(entries, minimumSimilarity) {
110
+ return entries
111
+ .filter((entry) => entry.similarity >= minimumSimilarity)
112
+ .sort((a, b) => b.similarity - a.similarity);
113
+ }
@@ -0,0 +1,179 @@
1
+ /**
2
+ * HowLongToBeat TypeScript types
3
+ */
4
+ /**
5
+ * Search modifiers for filtering game results
6
+ */
7
+ export declare enum SearchModifiers {
8
+ /** No modifier - include all results */
9
+ NONE = "",
10
+ /** Only show DLC content */
11
+ ISOLATE_DLC = "only_dlc",
12
+ /** Only show mods */
13
+ ISOLATE_MODS = "only_mods",
14
+ /** Only show hacks */
15
+ ISOLATE_HACKS = "only_hacks",
16
+ /** Hide DLC from results */
17
+ HIDE_DLC = "hide_dlc"
18
+ }
19
+ /**
20
+ * Similarity algorithm to use for filtering results
21
+ */
22
+ export type SimilarityAlgorithm = "gestalt" | "levenshtein";
23
+ /**
24
+ * Options for HowLongToBeat constructor
25
+ */
26
+ export interface HowLongToBeatOptions {
27
+ /** Minimum similarity threshold (0-1). Default: 0.4 */
28
+ minimumSimilarity?: number;
29
+ /** Auto-nullify irrelevant time fields based on game type. Default: false */
30
+ autoFilterTimes?: boolean;
31
+ /** Similarity algorithm to use. Default: "gestalt" */
32
+ similarityAlgorithm?: SimilarityAlgorithm;
33
+ }
34
+ /**
35
+ * Represents a game entry from HowLongToBeat
36
+ */
37
+ export interface HowLongToBeatEntry {
38
+ /** Unique game ID on HLTB */
39
+ gameId: number;
40
+ /** Game name */
41
+ gameName: string | null;
42
+ /** Alternative name/alias */
43
+ gameAlias: string | null;
44
+ /** Type of content: "game" or "dlc" */
45
+ gameType: string | null;
46
+ /** URL to the game's cover image */
47
+ gameImageUrl: string | null;
48
+ /** Direct link to the game's HLTB page */
49
+ gameWebLink: string;
50
+ /** Review score (0-100) */
51
+ reviewScore: number | null;
52
+ /** Developer name */
53
+ profileDev: string | null;
54
+ /** Available platforms */
55
+ profilePlatforms: string[] | null;
56
+ /** Release year */
57
+ releaseWorld: number | null;
58
+ /** Main story completion time */
59
+ mainStory: number | null;
60
+ /** Main story + extras completion time */
61
+ mainExtra: number | null;
62
+ /** 100% completionist time */
63
+ completionist: number | null;
64
+ /** Average of all playstyles */
65
+ allStyles: number | null;
66
+ /** Co-op completion time */
67
+ coopTime: number | null;
68
+ /** Multiplayer time */
69
+ mpTime: number | null;
70
+ /** Combined single/multi game */
71
+ complexityLvlCombine: boolean;
72
+ /** Has singleplayer content */
73
+ complexityLvlSp: boolean;
74
+ /** Has co-op content */
75
+ complexityLvlCo: boolean;
76
+ /** Has multiplayer content */
77
+ complexityLvlMp: boolean;
78
+ /** Similarity score to search query (0-1) */
79
+ similarity: number;
80
+ /** Full raw JSON response from HLTB */
81
+ jsonContent: Record<string, unknown>;
82
+ }
83
+ /**
84
+ * Raw game data from HLTB API response
85
+ */
86
+ export interface HLTBRawGame {
87
+ game_id: number;
88
+ game_name: string;
89
+ game_name_date: number;
90
+ game_alias: string;
91
+ game_type: string;
92
+ game_image: string;
93
+ comp_lvl_combine: number;
94
+ comp_lvl_sp: number;
95
+ comp_lvl_co: number;
96
+ comp_lvl_mp: number;
97
+ comp_lvl_spd: number;
98
+ comp_main: number;
99
+ comp_plus: number;
100
+ comp_100: number;
101
+ comp_all: number;
102
+ comp_main_count: number;
103
+ comp_plus_count: number;
104
+ comp_100_count: number;
105
+ comp_all_count: number;
106
+ invested_co: number;
107
+ invested_mp: number;
108
+ invested_co_count: number;
109
+ invested_mp_count: number;
110
+ count_comp: number;
111
+ count_speedrun: number;
112
+ count_backlog: number;
113
+ count_review: number;
114
+ review_score: number;
115
+ count_playing: number;
116
+ count_retired: number;
117
+ profile_dev: string;
118
+ profile_popular: number;
119
+ profile_steam: number;
120
+ profile_platform: string;
121
+ release_world: number;
122
+ [key: string]: unknown;
123
+ }
124
+ /**
125
+ * HLTB API search response structure
126
+ */
127
+ export interface HLTBSearchResponse {
128
+ color: string;
129
+ title: string;
130
+ category: string;
131
+ count: number;
132
+ pageCurrent: number;
133
+ pageTotal: number;
134
+ pageSize: number;
135
+ data: HLTBRawGame[];
136
+ }
137
+ /**
138
+ * Search request body for HLTB API
139
+ */
140
+ export interface HLTBSearchRequest {
141
+ searchType: string;
142
+ searchTerms: string[];
143
+ searchPage: number;
144
+ size: number;
145
+ searchOptions: {
146
+ games: {
147
+ userId: number;
148
+ platform: string;
149
+ sortCategory: string;
150
+ rangeCategory: string;
151
+ rangeTime: {
152
+ min: number;
153
+ max: number;
154
+ };
155
+ gameplay: {
156
+ perspective: string;
157
+ flow: string;
158
+ genre: string;
159
+ difficulty: string;
160
+ };
161
+ rangeYear: {
162
+ max: string;
163
+ min: string;
164
+ };
165
+ modifier: string;
166
+ };
167
+ users: {
168
+ sortCategory: string;
169
+ };
170
+ lists: {
171
+ sortCategory: string;
172
+ };
173
+ filter: string;
174
+ sort: number;
175
+ randomizer: number;
176
+ };
177
+ useCache: boolean;
178
+ }
179
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/src/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * HowLongToBeat TypeScript types
3
+ */
4
+ /**
5
+ * Search modifiers for filtering game results
6
+ */
7
+ export var SearchModifiers;
8
+ (function (SearchModifiers) {
9
+ /** No modifier - include all results */
10
+ SearchModifiers["NONE"] = "";
11
+ /** Only show DLC content */
12
+ SearchModifiers["ISOLATE_DLC"] = "only_dlc";
13
+ /** Only show mods */
14
+ SearchModifiers["ISOLATE_MODS"] = "only_mods";
15
+ /** Only show hacks */
16
+ SearchModifiers["ISOLATE_HACKS"] = "only_hacks";
17
+ /** Hide DLC from results */
18
+ SearchModifiers["HIDE_DLC"] = "hide_dlc";
19
+ })(SearchModifiers || (SearchModifiers = {}));
@@ -0,0 +1,28 @@
1
+ /**
2
+ * String similarity algorithms for HowLongToBeat
3
+ *
4
+ * Provides both Gestalt pattern matching (like Python's difflib.SequenceMatcher)
5
+ * and Levenshtein distance-based similarity.
6
+ */
7
+ import type { SimilarityAlgorithm } from "../types.js";
8
+ /**
9
+ * Calculate similarity using the specified algorithm
10
+ */
11
+ export declare function calculateSimilarity(a: string, b: string, algorithm?: SimilarityAlgorithm): number;
12
+ /**
13
+ * Gestalt Pattern Matching similarity (like Python's difflib.SequenceMatcher)
14
+ *
15
+ * This implements the Ratcliff/Obershelp algorithm which finds the longest
16
+ * common substring and recursively processes the remaining parts.
17
+ */
18
+ export declare function gestaltSimilarity(a: string, b: string): number;
19
+ /**
20
+ * Calculate similarity based on Levenshtein distance
21
+ * Returns a value between 0 and 1, where 1 means identical strings
22
+ */
23
+ export declare function levenshteinSimilarity(a: string, b: string): number;
24
+ /**
25
+ * Create a similarity calculator function with a specific algorithm
26
+ */
27
+ export declare function createSimilarityCalculator(algorithm?: SimilarityAlgorithm): (a: string, b: string) => number;
28
+ //# sourceMappingURL=similarity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"similarity.d.ts","sourceRoot":"","sources":["../../../src/src/utils/similarity.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,SAAS,GAAE,mBAA+B,GACzC,MAAM,CAKR;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAM9D;AAuGD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAOlE;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,SAAS,GAAE,mBAA+B,GACzC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,MAAM,CAElC"}