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,258 @@
1
+ "use strict";
2
+ /**
3
+ * HTTP client for HowLongToBeat API
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.buildSearchRequest = buildSearchRequest;
7
+ exports.executeSearch = executeSearch;
8
+ exports.fetchGamePage = fetchGamePage;
9
+ exports.extractGameTitle = extractGameTitle;
10
+ exports.getBaseUrl = getBaseUrl;
11
+ exports.clearCache = clearCache;
12
+ const HLTB_BASE_URL = "https://howlongtobeat.com";
13
+ const HLTB_SEARCH_URL = `${HLTB_BASE_URL}/api/search`;
14
+ const HLTB_GAME_URL = `${HLTB_BASE_URL}/game`;
15
+ /**
16
+ * List of User-Agent strings for rotation
17
+ */
18
+ const USER_AGENTS = [
19
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
20
+ "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",
21
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0",
22
+ "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",
23
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
24
+ "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",
25
+ ];
26
+ /**
27
+ * Get a random User-Agent string
28
+ */
29
+ function getRandomUserAgent() {
30
+ return USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)];
31
+ }
32
+ /**
33
+ * Cached auth token and its expiry
34
+ */
35
+ let cachedToken = null;
36
+ let tokenExpiry = 0;
37
+ const TOKEN_TTL_MS = 5 * 60 * 1000; // 5 minutes
38
+ /**
39
+ * Cached search API URL
40
+ */
41
+ let cachedSearchUrl = null;
42
+ /**
43
+ * Fetch auth token from HLTB
44
+ */
45
+ async function fetchAuthToken() {
46
+ // Check cache first
47
+ if (cachedToken && Date.now() < tokenExpiry) {
48
+ return cachedToken;
49
+ }
50
+ try {
51
+ const timestamp = Date.now();
52
+ const response = await fetch(`${HLTB_BASE_URL}/api/search/init?t=${timestamp}`, {
53
+ headers: {
54
+ "User-Agent": getRandomUserAgent(),
55
+ "Referer": HLTB_BASE_URL,
56
+ },
57
+ });
58
+ if (!response.ok) {
59
+ // TODO: implement proper error handling
60
+ console.error(`Failed to fetch auth token: ${response.status}`);
61
+ return null;
62
+ }
63
+ const data = (await response.json());
64
+ cachedToken = data.token || null;
65
+ tokenExpiry = Date.now() + TOKEN_TTL_MS;
66
+ return cachedToken;
67
+ }
68
+ catch (error) {
69
+ // TODO: implement proper error handling
70
+ console.error("Error fetching auth token:", error);
71
+ return null;
72
+ }
73
+ }
74
+ /**
75
+ * Discover the dynamic search API URL from HLTB's JavaScript bundles
76
+ */
77
+ async function discoverSearchUrl() {
78
+ if (cachedSearchUrl) {
79
+ return cachedSearchUrl;
80
+ }
81
+ try {
82
+ // Fetch the main page
83
+ const response = await fetch(HLTB_BASE_URL, {
84
+ headers: {
85
+ "User-Agent": getRandomUserAgent(),
86
+ },
87
+ });
88
+ if (!response.ok) {
89
+ // TODO: implement proper error handling
90
+ console.error(`Failed to fetch main page: ${response.status}`);
91
+ return HLTB_SEARCH_URL;
92
+ }
93
+ const html = await response.text();
94
+ // Find _app-*.js bundle URLs
95
+ const scriptPattern = /_app-[a-zA-Z0-9]+\.js/g;
96
+ const scripts = html.match(scriptPattern);
97
+ if (!scripts || scripts.length === 0) {
98
+ return HLTB_SEARCH_URL;
99
+ }
100
+ // Try to find the search endpoint in the scripts
101
+ for (const script of scripts) {
102
+ try {
103
+ const scriptUrl = `${HLTB_BASE_URL}/_next/static/chunks/pages/${script}`;
104
+ const scriptResponse = await fetch(scriptUrl, {
105
+ headers: {
106
+ "User-Agent": getRandomUserAgent(),
107
+ },
108
+ });
109
+ if (!scriptResponse.ok)
110
+ continue;
111
+ const scriptContent = await scriptResponse.text();
112
+ // Look for the search API path pattern
113
+ // Common patterns: "/api/search/", "api/search", etc.
114
+ const searchPathPattern = /["']([^"']*api\/search[^"']*)["']/g;
115
+ const matches = [...scriptContent.matchAll(searchPathPattern)];
116
+ for (const match of matches) {
117
+ const path = match[1];
118
+ if (path && !path.includes("init") && path.startsWith("/")) {
119
+ cachedSearchUrl = `${HLTB_BASE_URL}${path}`;
120
+ return cachedSearchUrl;
121
+ }
122
+ }
123
+ }
124
+ catch {
125
+ // TODO: implement proper error handling
126
+ continue;
127
+ }
128
+ }
129
+ }
130
+ catch (error) {
131
+ // TODO: implement proper error handling
132
+ console.error("Error discovering search URL:", error);
133
+ }
134
+ // Fall back to known URL
135
+ cachedSearchUrl = HLTB_SEARCH_URL;
136
+ return cachedSearchUrl;
137
+ }
138
+ /**
139
+ * Build search request body
140
+ */
141
+ function buildSearchRequest(searchTerms, modifier = "", page = 1, size = 20) {
142
+ return {
143
+ searchType: "games",
144
+ searchTerms,
145
+ searchPage: page,
146
+ size,
147
+ searchOptions: {
148
+ games: {
149
+ userId: 0,
150
+ platform: "",
151
+ sortCategory: "popular",
152
+ rangeCategory: "main",
153
+ rangeTime: { min: 0, max: 0 },
154
+ gameplay: {
155
+ perspective: "",
156
+ flow: "",
157
+ genre: "",
158
+ difficulty: "",
159
+ },
160
+ rangeYear: { max: "", min: "" },
161
+ modifier,
162
+ },
163
+ users: { sortCategory: "postcount" },
164
+ lists: { sortCategory: "follows" },
165
+ filter: "",
166
+ sort: 0,
167
+ randomizer: 0,
168
+ },
169
+ useCache: true,
170
+ };
171
+ }
172
+ /**
173
+ * Execute a search request to HLTB API
174
+ */
175
+ async function executeSearch(gameName, modifier = "") {
176
+ const token = await fetchAuthToken();
177
+ if (!token) {
178
+ // TODO: implement proper error handling
179
+ return null;
180
+ }
181
+ const searchUrl = await discoverSearchUrl();
182
+ const searchTerms = gameName.split(" ").filter((term) => term.length > 0);
183
+ const requestBody = buildSearchRequest(searchTerms, modifier);
184
+ try {
185
+ const response = await fetch(searchUrl, {
186
+ method: "POST",
187
+ headers: {
188
+ "Content-Type": "application/json",
189
+ "User-Agent": getRandomUserAgent(),
190
+ "Referer": HLTB_BASE_URL,
191
+ "x-auth-token": token,
192
+ },
193
+ body: JSON.stringify(requestBody),
194
+ });
195
+ if (!response.ok) {
196
+ // TODO: implement proper error handling
197
+ console.error(`Search request failed: ${response.status}`);
198
+ return null;
199
+ }
200
+ return await response.json();
201
+ }
202
+ catch (error) {
203
+ // TODO: implement proper error handling
204
+ console.error("Error executing search:", error);
205
+ return null;
206
+ }
207
+ }
208
+ /**
209
+ * Fetch game page HTML to extract game title
210
+ */
211
+ async function fetchGamePage(gameId) {
212
+ try {
213
+ const response = await fetch(`${HLTB_GAME_URL}/${gameId}`, {
214
+ headers: {
215
+ "User-Agent": getRandomUserAgent(),
216
+ "Referer": HLTB_BASE_URL,
217
+ },
218
+ });
219
+ if (!response.ok) {
220
+ // TODO: implement proper error handling
221
+ return null;
222
+ }
223
+ return await response.text();
224
+ }
225
+ catch (error) {
226
+ // TODO: implement proper error handling
227
+ console.error("Error fetching game page:", error);
228
+ return null;
229
+ }
230
+ }
231
+ /**
232
+ * Extract game title from HLTB game page HTML
233
+ */
234
+ function extractGameTitle(html) {
235
+ // Title format: "How long is {GAME_NAME}? | HowLongToBeat"
236
+ // Note: title tag may have attributes like data-next-head=""
237
+ const titlePattern = /<title[^>]*>How long is ([^?]+)\?[^<]*<\/title>/i;
238
+ const match = html.match(titlePattern);
239
+ if (match && match[1]) {
240
+ return match[1].trim();
241
+ }
242
+ // TODO: implement proper error handling
243
+ return null;
244
+ }
245
+ /**
246
+ * Get base URL for constructing image and web links
247
+ */
248
+ function getBaseUrl() {
249
+ return HLTB_BASE_URL;
250
+ }
251
+ /**
252
+ * Clear cached token and search URL (useful for testing)
253
+ */
254
+ function clearCache() {
255
+ cachedToken = null;
256
+ tokenExpiry = 0;
257
+ cachedSearchUrl = null;
258
+ }
@@ -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,118 @@
1
+ "use strict";
2
+ /**
3
+ * JSON response parser for HowLongToBeat API
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.parseGameEntry = parseGameEntry;
7
+ exports.parseGameEntries = parseGameEntries;
8
+ exports.filterBySimilarity = filterBySimilarity;
9
+ const client_js_1 = require("../http/client.js");
10
+ const HLTB_IMAGE_URL = "https://howlongtobeat.com/games";
11
+ /**
12
+ * Convert seconds to hours with one decimal place
13
+ */
14
+ function secondsToHours(seconds) {
15
+ if (seconds <= 0) {
16
+ return null;
17
+ }
18
+ return Math.round((seconds / 3600) * 10) / 10;
19
+ }
20
+ /**
21
+ * Parse platforms string into array
22
+ */
23
+ function parsePlatforms(platformString) {
24
+ if (!platformString || platformString.trim() === "") {
25
+ return null;
26
+ }
27
+ return platformString.split(",").map((p) => p.trim()).filter((p) => p.length > 0);
28
+ }
29
+ /**
30
+ * Convert a raw HLTB game object to HowLongToBeatEntry
31
+ */
32
+ function parseGameEntry(raw, similarity = 0, autoFilterTimes = false) {
33
+ const baseUrl = (0, client_js_1.getBaseUrl)();
34
+ // Determine game type flags
35
+ const complexityLvlCombine = raw.comp_lvl_combine === 1;
36
+ const complexityLvlSp = raw.comp_lvl_sp === 1;
37
+ const complexityLvlCo = raw.comp_lvl_co === 1;
38
+ const complexityLvlMp = raw.comp_lvl_mp === 1;
39
+ // Parse times
40
+ let mainStory = secondsToHours(raw.comp_main);
41
+ let mainExtra = secondsToHours(raw.comp_plus);
42
+ let completionist = secondsToHours(raw.comp_100);
43
+ let allStyles = secondsToHours(raw.comp_all);
44
+ let coopTime = secondsToHours(raw.invested_co);
45
+ let mpTime = secondsToHours(raw.invested_mp);
46
+ // Auto-filter times based on game type
47
+ if (autoFilterTimes) {
48
+ // If no singleplayer, nullify SP times
49
+ if (!complexityLvlSp && !complexityLvlCombine) {
50
+ mainStory = null;
51
+ mainExtra = null;
52
+ completionist = null;
53
+ allStyles = null;
54
+ }
55
+ // If no co-op, nullify co-op time
56
+ if (!complexityLvlCo && !complexityLvlCombine) {
57
+ coopTime = null;
58
+ }
59
+ // If no multiplayer, nullify MP time
60
+ if (!complexityLvlMp && !complexityLvlCombine) {
61
+ mpTime = null;
62
+ }
63
+ }
64
+ // Build image URL
65
+ let gameImageUrl = null;
66
+ if (raw.game_image) {
67
+ gameImageUrl = `${HLTB_IMAGE_URL}/${raw.game_image}`;
68
+ }
69
+ return {
70
+ gameId: raw.game_id,
71
+ gameName: raw.game_name || null,
72
+ gameAlias: raw.game_alias || null,
73
+ gameType: raw.game_type || null,
74
+ gameImageUrl,
75
+ gameWebLink: `${baseUrl}/game/${raw.game_id}`,
76
+ reviewScore: raw.review_score > 0 ? raw.review_score : null,
77
+ profileDev: raw.profile_dev || null,
78
+ profilePlatforms: parsePlatforms(raw.profile_platform),
79
+ releaseWorld: raw.release_world > 0 ? raw.release_world : null,
80
+ mainStory,
81
+ mainExtra,
82
+ completionist,
83
+ allStyles,
84
+ coopTime,
85
+ mpTime,
86
+ complexityLvlCombine,
87
+ complexityLvlSp,
88
+ complexityLvlCo,
89
+ complexityLvlMp,
90
+ similarity,
91
+ jsonContent: raw,
92
+ };
93
+ }
94
+ /**
95
+ * Parse an array of raw games into HowLongToBeatEntry array
96
+ */
97
+ function parseGameEntries(rawGames, searchQuery, autoFilterTimes, calculateSimilarity) {
98
+ const normalizedQuery = searchQuery.toLowerCase().trim();
99
+ return rawGames.map((raw) => {
100
+ // Calculate similarity against game name and alias
101
+ const nameSimilarity = raw.game_name
102
+ ? calculateSimilarity(normalizedQuery, raw.game_name.toLowerCase())
103
+ : 0;
104
+ const aliasSimilarity = raw.game_alias
105
+ ? calculateSimilarity(normalizedQuery, raw.game_alias.toLowerCase())
106
+ : 0;
107
+ const similarity = Math.max(nameSimilarity, aliasSimilarity);
108
+ return parseGameEntry(raw, similarity, autoFilterTimes);
109
+ });
110
+ }
111
+ /**
112
+ * Filter entries by minimum similarity threshold
113
+ */
114
+ function filterBySimilarity(entries, minimumSimilarity) {
115
+ return entries
116
+ .filter((entry) => entry.similarity >= minimumSimilarity)
117
+ .sort((a, b) => b.similarity - a.similarity);
118
+ }
@@ -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,22 @@
1
+ "use strict";
2
+ /**
3
+ * HowLongToBeat TypeScript types
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SearchModifiers = void 0;
7
+ /**
8
+ * Search modifiers for filtering game results
9
+ */
10
+ var SearchModifiers;
11
+ (function (SearchModifiers) {
12
+ /** No modifier - include all results */
13
+ SearchModifiers["NONE"] = "";
14
+ /** Only show DLC content */
15
+ SearchModifiers["ISOLATE_DLC"] = "only_dlc";
16
+ /** Only show mods */
17
+ SearchModifiers["ISOLATE_MODS"] = "only_mods";
18
+ /** Only show hacks */
19
+ SearchModifiers["ISOLATE_HACKS"] = "only_hacks";
20
+ /** Hide DLC from results */
21
+ SearchModifiers["HIDE_DLC"] = "hide_dlc";
22
+ })(SearchModifiers || (exports.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"}