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.
- package/LICENSE +21 -0
- package/README.md +157 -0
- package/esm/mod.d.ts +30 -0
- package/esm/mod.d.ts.map +1 -0
- package/esm/mod.js +31 -0
- package/esm/package.json +3 -0
- package/esm/src/HowLongToBeat.d.ts +63 -0
- package/esm/src/HowLongToBeat.d.ts.map +1 -0
- package/esm/src/HowLongToBeat.js +142 -0
- package/esm/src/http/client.d.ts +29 -0
- package/esm/src/http/client.d.ts.map +1 -0
- package/esm/src/http/client.js +250 -0
- package/esm/src/parser/json.d.ts +17 -0
- package/esm/src/parser/json.d.ts.map +1 -0
- package/esm/src/parser/json.js +113 -0
- package/esm/src/types.d.ts +179 -0
- package/esm/src/types.d.ts.map +1 -0
- package/esm/src/types.js +19 -0
- package/esm/src/utils/similarity.d.ts +28 -0
- package/esm/src/utils/similarity.d.ts.map +1 -0
- package/esm/src/utils/similarity.js +127 -0
- package/package.json +38 -0
- package/script/mod.d.ts +30 -0
- package/script/mod.d.ts.map +1 -0
- package/script/mod.js +39 -0
- package/script/package.json +3 -0
- package/script/src/HowLongToBeat.d.ts +63 -0
- package/script/src/HowLongToBeat.d.ts.map +1 -0
- package/script/src/HowLongToBeat.js +146 -0
- package/script/src/http/client.d.ts +29 -0
- package/script/src/http/client.d.ts.map +1 -0
- package/script/src/http/client.js +258 -0
- package/script/src/parser/json.d.ts +17 -0
- package/script/src/parser/json.d.ts.map +1 -0
- package/script/src/parser/json.js +118 -0
- package/script/src/types.d.ts +179 -0
- package/script/src/types.d.ts.map +1 -0
- package/script/src/types.js +22 -0
- package/script/src/utils/similarity.d.ts +28 -0
- package/script/src/utils/similarity.d.ts.map +1 -0
- package/script/src/utils/similarity.js +133 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# howlongtobeat-core
|
|
2
|
+
|
|
3
|
+
A TypeScript/Deno library to retrieve game completion times from
|
|
4
|
+
[HowLongToBeat.com](https://howlongtobeat.com).
|
|
5
|
+
|
|
6
|
+
[](https://jsr.io/howlongtobeat-core)
|
|
7
|
+
[](https://www.npmjs.com/package/howlongtobeat-core)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- 🎮 Search games by name or HLTB ID
|
|
13
|
+
- ⏱️ Get completion times (Main Story, Main + Extra, Completionist)
|
|
14
|
+
- 🔍 Dual similarity algorithms (Gestalt & Levenshtein)
|
|
15
|
+
- 🎯 Filter by DLC, mods, or hacks
|
|
16
|
+
- 📦 Works with Deno, Node.js, and browsers
|
|
17
|
+
- 🔒 TypeScript-first with full type definitions
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
### Deno
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { HowLongToBeat } from "jsr:howlongtobeat-core";
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Node.js / npm
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install howlongtobeat-core
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { HowLongToBeat } from "howlongtobeat-core";
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
### Search by Name
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { HowLongToBeat } from "howlongtobeat-core";
|
|
43
|
+
|
|
44
|
+
const hltb = new HowLongToBeat();
|
|
45
|
+
const results = await hltb.search("Elden Ring");
|
|
46
|
+
|
|
47
|
+
if (results && results.length > 0) {
|
|
48
|
+
const game = results[0];
|
|
49
|
+
console.log(`${game.gameName}`);
|
|
50
|
+
console.log(` Main Story: ${game.mainStory} hours`);
|
|
51
|
+
console.log(` Main + Extra: ${game.mainExtra} hours`);
|
|
52
|
+
console.log(` Completionist: ${game.completionist} hours`);
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Search by ID
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
const game = await hltb.searchById(68151); // Elden Ring
|
|
60
|
+
console.log(game?.gameName); // "Elden Ring"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### With Options
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
const hltb = new HowLongToBeat({
|
|
67
|
+
minimumSimilarity: 0.5, // Filter results below 50% similarity
|
|
68
|
+
autoFilterTimes: true, // Nullify irrelevant times (e.g., SP times for MP-only games)
|
|
69
|
+
similarityAlgorithm: "gestalt", // or "levenshtein"
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Filter DLC/Mods
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { HowLongToBeat, SearchModifiers } from "howlongtobeat-core";
|
|
77
|
+
|
|
78
|
+
const hltb = new HowLongToBeat();
|
|
79
|
+
const dlcOnly = await hltb.search("Dark Souls", SearchModifiers.ISOLATE_DLC);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## API Reference
|
|
83
|
+
|
|
84
|
+
### `HowLongToBeat`
|
|
85
|
+
|
|
86
|
+
#### Constructor Options
|
|
87
|
+
|
|
88
|
+
| Option | Type | Default | Description |
|
|
89
|
+
| --------------------- | ---------------------------- | ----------- | ------------------------------------------------------ |
|
|
90
|
+
| `minimumSimilarity` | `number` | `0.4` | Filter results below this similarity threshold (0-1) |
|
|
91
|
+
| `autoFilterTimes` | `boolean` | `false` | Auto-nullify irrelevant time fields based on game type |
|
|
92
|
+
| `similarityAlgorithm` | `"gestalt" \| "levenshtein"` | `"gestalt"` | Algorithm for string similarity matching |
|
|
93
|
+
|
|
94
|
+
#### Methods
|
|
95
|
+
|
|
96
|
+
| Method | Returns | Description |
|
|
97
|
+
| ------------------------- | --------------------------------------- | -------------------- |
|
|
98
|
+
| `search(name, modifier?)` | `Promise<HowLongToBeatEntry[] \| null>` | Search games by name |
|
|
99
|
+
| `searchById(id)` | `Promise<HowLongToBeatEntry \| null>` | Get game by HLTB ID |
|
|
100
|
+
|
|
101
|
+
### `HowLongToBeatEntry`
|
|
102
|
+
|
|
103
|
+
| Property | Type | Description |
|
|
104
|
+
| ------------------ | ------------------ | ---------------------------- |
|
|
105
|
+
| `gameId` | `number` | HLTB game ID |
|
|
106
|
+
| `gameName` | `string \| null` | Game title |
|
|
107
|
+
| `gameImageUrl` | `string \| null` | Cover image URL |
|
|
108
|
+
| `gameWebLink` | `string` | Link to HLTB page |
|
|
109
|
+
| `mainStory` | `number \| null` | Main story time (hours) |
|
|
110
|
+
| `mainExtra` | `number \| null` | Main + extras time (hours) |
|
|
111
|
+
| `completionist` | `number \| null` | 100% completion time (hours) |
|
|
112
|
+
| `allStyles` | `number \| null` | Average of all playstyles |
|
|
113
|
+
| `similarity` | `number` | Match similarity (0-1) |
|
|
114
|
+
| `reviewScore` | `number \| null` | User review score |
|
|
115
|
+
| `profilePlatforms` | `string[] \| null` | Available platforms |
|
|
116
|
+
| `releaseWorld` | `number \| null` | Release year |
|
|
117
|
+
|
|
118
|
+
### `SearchModifiers`
|
|
119
|
+
|
|
120
|
+
| Value | Description |
|
|
121
|
+
| --------------- | --------------------- |
|
|
122
|
+
| `NONE` | No filter (default) |
|
|
123
|
+
| `ISOLATE_DLC` | Only show DLC |
|
|
124
|
+
| `ISOLATE_MODS` | Only show mods |
|
|
125
|
+
| `ISOLATE_HACKS` | Only show hacks |
|
|
126
|
+
| `HIDE_DLC` | Hide DLC from results |
|
|
127
|
+
|
|
128
|
+
## Development
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
# Run tests
|
|
132
|
+
deno task test
|
|
133
|
+
|
|
134
|
+
# Run unit tests only
|
|
135
|
+
deno task test:unit
|
|
136
|
+
|
|
137
|
+
# Run integration tests (requires network)
|
|
138
|
+
deno task test:integration
|
|
139
|
+
|
|
140
|
+
# Type check
|
|
141
|
+
deno task check
|
|
142
|
+
|
|
143
|
+
# Format code
|
|
144
|
+
deno task fmt
|
|
145
|
+
|
|
146
|
+
# Lint
|
|
147
|
+
deno task lint
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Credits
|
|
151
|
+
|
|
152
|
+
This is a TypeScript port of
|
|
153
|
+
[ScrappyCocco/HowLongToBeat-PythonAPI](https://github.com/ScrappyCocco/HowLongToBeat-PythonAPI).
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
MIT © 2026
|
package/esm/mod.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HowLongToBeat API for Deno/Node.js
|
|
3
|
+
*
|
|
4
|
+
* A TypeScript wrapper for the HowLongToBeat.com website.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { HowLongToBeat, SearchModifiers } from "@tapan/howlongtobeat";
|
|
9
|
+
*
|
|
10
|
+
* const hltb = new HowLongToBeat({ minimumSimilarity: 0.5 });
|
|
11
|
+
*
|
|
12
|
+
* // Search by name
|
|
13
|
+
* const results = await hltb.search("Elden Ring");
|
|
14
|
+
* console.log(results?.[0]?.mainStory); // e.g., 50.5 hours
|
|
15
|
+
*
|
|
16
|
+
* // Search by ID
|
|
17
|
+
* const game = await hltb.searchById(10270);
|
|
18
|
+
* console.log(game?.gameName); // "Elden Ring"
|
|
19
|
+
*
|
|
20
|
+
* // Filter DLC
|
|
21
|
+
* const dlcOnly = await hltb.search("Dark Souls", SearchModifiers.ISOLATE_DLC);
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @module
|
|
25
|
+
*/
|
|
26
|
+
export { HowLongToBeat } from "./src/HowLongToBeat.js";
|
|
27
|
+
export { SearchModifiers } from "./src/types.js";
|
|
28
|
+
export type { HowLongToBeatEntry, HowLongToBeatOptions, SimilarityAlgorithm } from "./src/types.js";
|
|
29
|
+
export { calculateSimilarity, gestaltSimilarity, levenshteinSimilarity } from "./src/utils/similarity.js";
|
|
30
|
+
//# sourceMappingURL=mod.d.ts.map
|
package/esm/mod.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAGvD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,YAAY,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAGpG,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC"}
|
package/esm/mod.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HowLongToBeat API for Deno/Node.js
|
|
3
|
+
*
|
|
4
|
+
* A TypeScript wrapper for the HowLongToBeat.com website.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { HowLongToBeat, SearchModifiers } from "@tapan/howlongtobeat";
|
|
9
|
+
*
|
|
10
|
+
* const hltb = new HowLongToBeat({ minimumSimilarity: 0.5 });
|
|
11
|
+
*
|
|
12
|
+
* // Search by name
|
|
13
|
+
* const results = await hltb.search("Elden Ring");
|
|
14
|
+
* console.log(results?.[0]?.mainStory); // e.g., 50.5 hours
|
|
15
|
+
*
|
|
16
|
+
* // Search by ID
|
|
17
|
+
* const game = await hltb.searchById(10270);
|
|
18
|
+
* console.log(game?.gameName); // "Elden Ring"
|
|
19
|
+
*
|
|
20
|
+
* // Filter DLC
|
|
21
|
+
* const dlcOnly = await hltb.search("Dark Souls", SearchModifiers.ISOLATE_DLC);
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @module
|
|
25
|
+
*/
|
|
26
|
+
// Main class
|
|
27
|
+
export { HowLongToBeat } from "./src/HowLongToBeat.js";
|
|
28
|
+
// Types and enums
|
|
29
|
+
export { SearchModifiers } from "./src/types.js";
|
|
30
|
+
// Utility exports for advanced usage
|
|
31
|
+
export { calculateSimilarity, gestaltSimilarity, levenshteinSimilarity } from "./src/utils/similarity.js";
|
package/esm/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main HowLongToBeat class
|
|
3
|
+
*
|
|
4
|
+
* Provides methods to search for games on HowLongToBeat.com
|
|
5
|
+
*/
|
|
6
|
+
import type { HowLongToBeatEntry, HowLongToBeatOptions } from "./types.js";
|
|
7
|
+
import { SearchModifiers } from "./types.js";
|
|
8
|
+
/**
|
|
9
|
+
* HowLongToBeat API wrapper
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* const hltb = new HowLongToBeat();
|
|
14
|
+
* const results = await hltb.search("The Witcher 3");
|
|
15
|
+
* console.log(results);
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare class HowLongToBeat {
|
|
19
|
+
private readonly minimumSimilarity;
|
|
20
|
+
private readonly autoFilterTimes;
|
|
21
|
+
private readonly similarityAlgorithm;
|
|
22
|
+
private readonly similarityCalculator;
|
|
23
|
+
/**
|
|
24
|
+
* Create a new HowLongToBeat instance
|
|
25
|
+
*
|
|
26
|
+
* @param options - Configuration options
|
|
27
|
+
*/
|
|
28
|
+
constructor(options?: HowLongToBeatOptions);
|
|
29
|
+
/**
|
|
30
|
+
* Search for games by name
|
|
31
|
+
*
|
|
32
|
+
* @param gameName - The name of the game to search for
|
|
33
|
+
* @param modifier - Optional search modifier to filter results
|
|
34
|
+
* @returns Array of matching games sorted by similarity, or null on error
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* const hltb = new HowLongToBeat();
|
|
39
|
+
* const results = await hltb.search("Elden Ring");
|
|
40
|
+
* if (results && results.length > 0) {
|
|
41
|
+
* console.log(`Main story: ${results[0].mainStory} hours`);
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
search(gameName: string, modifier?: SearchModifiers): Promise<HowLongToBeatEntry[] | null>;
|
|
46
|
+
/**
|
|
47
|
+
* Search for a game by its HLTB ID
|
|
48
|
+
*
|
|
49
|
+
* @param gameId - The unique game ID on HowLongToBeat
|
|
50
|
+
* @returns The matching game entry, or null if not found
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* const hltb = new HowLongToBeat();
|
|
55
|
+
* const game = await hltb.searchById(10270); // Elden Ring
|
|
56
|
+
* if (game) {
|
|
57
|
+
* console.log(`${game.gameName}: ${game.mainStory} hours`);
|
|
58
|
+
* }
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
searchById(gameId: number): Promise<HowLongToBeatEntry | null>;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=HowLongToBeat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HowLongToBeat.d.ts","sourceRoot":"","sources":["../../src/src/HowLongToBeat.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,oBAAoB,EAErB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAkB7C;;;;;;;;;GASG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAU;IAC1C,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAsB;IAC1D,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAmC;IAExE;;;;OAIG;gBACS,OAAO,GAAE,oBAAyB;IAY9C;;;;;;;;;;;;;;;OAeG;IACG,MAAM,CACV,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,eAAsC,GAC/C,OAAO,CAAC,kBAAkB,EAAE,GAAG,IAAI,CAAC;IAuBvC;;;;;;;;;;;;;;OAcG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;CA8CrE"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main HowLongToBeat class
|
|
3
|
+
*
|
|
4
|
+
* Provides methods to search for games on HowLongToBeat.com
|
|
5
|
+
*/
|
|
6
|
+
import { SearchModifiers } from "./types.js";
|
|
7
|
+
import { executeSearch, extractGameTitle, fetchGamePage, } from "./http/client.js";
|
|
8
|
+
import { filterBySimilarity, parseGameEntries } from "./parser/json.js";
|
|
9
|
+
import { createSimilarityCalculator } from "./utils/similarity.js";
|
|
10
|
+
/**
|
|
11
|
+
* Default options for HowLongToBeat
|
|
12
|
+
*/
|
|
13
|
+
const DEFAULT_OPTIONS = {
|
|
14
|
+
minimumSimilarity: 0.4,
|
|
15
|
+
autoFilterTimes: false,
|
|
16
|
+
similarityAlgorithm: "gestalt",
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* HowLongToBeat API wrapper
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* const hltb = new HowLongToBeat();
|
|
24
|
+
* const results = await hltb.search("The Witcher 3");
|
|
25
|
+
* console.log(results);
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export class HowLongToBeat {
|
|
29
|
+
/**
|
|
30
|
+
* Create a new HowLongToBeat instance
|
|
31
|
+
*
|
|
32
|
+
* @param options - Configuration options
|
|
33
|
+
*/
|
|
34
|
+
constructor(options = {}) {
|
|
35
|
+
Object.defineProperty(this, "minimumSimilarity", {
|
|
36
|
+
enumerable: true,
|
|
37
|
+
configurable: true,
|
|
38
|
+
writable: true,
|
|
39
|
+
value: void 0
|
|
40
|
+
});
|
|
41
|
+
Object.defineProperty(this, "autoFilterTimes", {
|
|
42
|
+
enumerable: true,
|
|
43
|
+
configurable: true,
|
|
44
|
+
writable: true,
|
|
45
|
+
value: void 0
|
|
46
|
+
});
|
|
47
|
+
Object.defineProperty(this, "similarityAlgorithm", {
|
|
48
|
+
enumerable: true,
|
|
49
|
+
configurable: true,
|
|
50
|
+
writable: true,
|
|
51
|
+
value: void 0
|
|
52
|
+
});
|
|
53
|
+
Object.defineProperty(this, "similarityCalculator", {
|
|
54
|
+
enumerable: true,
|
|
55
|
+
configurable: true,
|
|
56
|
+
writable: true,
|
|
57
|
+
value: void 0
|
|
58
|
+
});
|
|
59
|
+
this.minimumSimilarity = options.minimumSimilarity ??
|
|
60
|
+
DEFAULT_OPTIONS.minimumSimilarity;
|
|
61
|
+
this.autoFilterTimes = options.autoFilterTimes ??
|
|
62
|
+
DEFAULT_OPTIONS.autoFilterTimes;
|
|
63
|
+
this.similarityAlgorithm = options.similarityAlgorithm ??
|
|
64
|
+
DEFAULT_OPTIONS.similarityAlgorithm;
|
|
65
|
+
this.similarityCalculator = createSimilarityCalculator(this.similarityAlgorithm);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Search for games by name
|
|
69
|
+
*
|
|
70
|
+
* @param gameName - The name of the game to search for
|
|
71
|
+
* @param modifier - Optional search modifier to filter results
|
|
72
|
+
* @returns Array of matching games sorted by similarity, or null on error
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```typescript
|
|
76
|
+
* const hltb = new HowLongToBeat();
|
|
77
|
+
* const results = await hltb.search("Elden Ring");
|
|
78
|
+
* if (results && results.length > 0) {
|
|
79
|
+
* console.log(`Main story: ${results[0].mainStory} hours`);
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
async search(gameName, modifier = SearchModifiers.NONE) {
|
|
84
|
+
if (!gameName || gameName.trim().length === 0) {
|
|
85
|
+
// TODO: implement proper error handling
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
const response = await executeSearch(gameName.trim(), modifier);
|
|
89
|
+
if (!response || !response.data) {
|
|
90
|
+
// TODO: implement proper error handling
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
const entries = parseGameEntries(response.data, gameName, this.autoFilterTimes, this.similarityCalculator);
|
|
94
|
+
return filterBySimilarity(entries, this.minimumSimilarity);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Search for a game by its HLTB ID
|
|
98
|
+
*
|
|
99
|
+
* @param gameId - The unique game ID on HowLongToBeat
|
|
100
|
+
* @returns The matching game entry, or null if not found
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```typescript
|
|
104
|
+
* const hltb = new HowLongToBeat();
|
|
105
|
+
* const game = await hltb.searchById(10270); // Elden Ring
|
|
106
|
+
* if (game) {
|
|
107
|
+
* console.log(`${game.gameName}: ${game.mainStory} hours`);
|
|
108
|
+
* }
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
async searchById(gameId) {
|
|
112
|
+
if (!gameId || gameId <= 0) {
|
|
113
|
+
// TODO: implement proper error handling
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
// First, fetch the game page to get the title
|
|
117
|
+
const html = await fetchGamePage(gameId);
|
|
118
|
+
if (!html) {
|
|
119
|
+
// TODO: implement proper error handling
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
const gameTitle = extractGameTitle(html);
|
|
123
|
+
if (!gameTitle) {
|
|
124
|
+
// TODO: implement proper error handling
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
// Search using the extracted title
|
|
128
|
+
const response = await executeSearch(gameTitle);
|
|
129
|
+
if (!response || !response.data) {
|
|
130
|
+
// TODO: implement proper error handling
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
// Find the exact game by ID
|
|
134
|
+
const matchingGame = response.data.find((game) => game.game_id === gameId);
|
|
135
|
+
if (!matchingGame) {
|
|
136
|
+
// TODO: implement proper error handling
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
const entries = parseGameEntries([matchingGame], gameTitle, this.autoFilterTimes, this.similarityCalculator);
|
|
140
|
+
return entries.length > 0 ? entries[0] : null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP client for HowLongToBeat API
|
|
3
|
+
*/
|
|
4
|
+
import type { HLTBSearchRequest, HLTBSearchResponse } from "../types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Build search request body
|
|
7
|
+
*/
|
|
8
|
+
export declare function buildSearchRequest(searchTerms: string[], modifier?: string, page?: number, size?: number): HLTBSearchRequest;
|
|
9
|
+
/**
|
|
10
|
+
* Execute a search request to HLTB API
|
|
11
|
+
*/
|
|
12
|
+
export declare function executeSearch(gameName: string, modifier?: string): Promise<HLTBSearchResponse | null>;
|
|
13
|
+
/**
|
|
14
|
+
* Fetch game page HTML to extract game title
|
|
15
|
+
*/
|
|
16
|
+
export declare function fetchGamePage(gameId: number): Promise<string | null>;
|
|
17
|
+
/**
|
|
18
|
+
* Extract game title from HLTB game page HTML
|
|
19
|
+
*/
|
|
20
|
+
export declare function extractGameTitle(html: string): string | null;
|
|
21
|
+
/**
|
|
22
|
+
* Get base URL for constructing image and web links
|
|
23
|
+
*/
|
|
24
|
+
export declare function getBaseUrl(): string;
|
|
25
|
+
/**
|
|
26
|
+
* Clear cached token and search URL (useful for testing)
|
|
27
|
+
*/
|
|
28
|
+
export declare function clearCache(): void;
|
|
29
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/src/http/client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAqJzE;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,MAAM,EAAE,EACrB,QAAQ,GAAE,MAAW,EACrB,IAAI,GAAE,MAAU,EAChB,IAAI,GAAE,MAAW,GAChB,iBAAiB,CA8BnB;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,MAAW,GACpB,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAmCpC;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAoB1E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAY5D;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,IAAI,CAIjC"}
|