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
|
@@ -0,0 +1,127 @@
|
|
|
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
|
+
/**
|
|
8
|
+
* Calculate similarity using the specified algorithm
|
|
9
|
+
*/
|
|
10
|
+
export function calculateSimilarity(a, b, algorithm = "gestalt") {
|
|
11
|
+
if (algorithm === "levenshtein") {
|
|
12
|
+
return levenshteinSimilarity(a, b);
|
|
13
|
+
}
|
|
14
|
+
return gestaltSimilarity(a, b);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Gestalt Pattern Matching similarity (like Python's difflib.SequenceMatcher)
|
|
18
|
+
*
|
|
19
|
+
* This implements the Ratcliff/Obershelp algorithm which finds the longest
|
|
20
|
+
* common substring and recursively processes the remaining parts.
|
|
21
|
+
*/
|
|
22
|
+
export function gestaltSimilarity(a, b) {
|
|
23
|
+
if (a === b)
|
|
24
|
+
return 1.0;
|
|
25
|
+
if (a.length === 0 || b.length === 0)
|
|
26
|
+
return 0.0;
|
|
27
|
+
const matches = countMatchingCharacters(a, b);
|
|
28
|
+
return (2.0 * matches) / (a.length + b.length);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Count matching characters using Ratcliff/Obershelp algorithm
|
|
32
|
+
*/
|
|
33
|
+
function countMatchingCharacters(a, b) {
|
|
34
|
+
const [lcs, aStart, aEnd, bStart, bEnd] = findLongestCommonSubstring(a, b);
|
|
35
|
+
if (lcs === 0) {
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
let count = lcs;
|
|
39
|
+
// Recursively process left parts
|
|
40
|
+
if (aStart > 0 && bStart > 0) {
|
|
41
|
+
count += countMatchingCharacters(a.substring(0, aStart), b.substring(0, bStart));
|
|
42
|
+
}
|
|
43
|
+
// Recursively process right parts
|
|
44
|
+
if (aEnd < a.length && bEnd < b.length) {
|
|
45
|
+
count += countMatchingCharacters(a.substring(aEnd), b.substring(bEnd));
|
|
46
|
+
}
|
|
47
|
+
return count;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Find the longest common substring between two strings
|
|
51
|
+
* Returns [length, aStart, aEnd, bStart, bEnd]
|
|
52
|
+
*/
|
|
53
|
+
function findLongestCommonSubstring(a, b) {
|
|
54
|
+
let longestLength = 0;
|
|
55
|
+
let aStart = 0;
|
|
56
|
+
let bStart = 0;
|
|
57
|
+
// Build a table of matching positions
|
|
58
|
+
const table = Array(a.length + 1)
|
|
59
|
+
.fill(null)
|
|
60
|
+
.map(() => Array(b.length + 1).fill(0));
|
|
61
|
+
for (let i = 1; i <= a.length; i++) {
|
|
62
|
+
for (let j = 1; j <= b.length; j++) {
|
|
63
|
+
if (a[i - 1] === b[j - 1]) {
|
|
64
|
+
table[i][j] = table[i - 1][j - 1] + 1;
|
|
65
|
+
if (table[i][j] > longestLength) {
|
|
66
|
+
longestLength = table[i][j];
|
|
67
|
+
aStart = i - longestLength;
|
|
68
|
+
bStart = j - longestLength;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return [
|
|
74
|
+
longestLength,
|
|
75
|
+
aStart,
|
|
76
|
+
aStart + longestLength,
|
|
77
|
+
bStart,
|
|
78
|
+
bStart + longestLength,
|
|
79
|
+
];
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Calculate Levenshtein distance between two strings
|
|
83
|
+
*/
|
|
84
|
+
function levenshteinDistance(a, b) {
|
|
85
|
+
if (a.length === 0)
|
|
86
|
+
return b.length;
|
|
87
|
+
if (b.length === 0)
|
|
88
|
+
return a.length;
|
|
89
|
+
const matrix = [];
|
|
90
|
+
// Initialize first column
|
|
91
|
+
for (let i = 0; i <= a.length; i++) {
|
|
92
|
+
matrix[i] = [i];
|
|
93
|
+
}
|
|
94
|
+
// Initialize first row
|
|
95
|
+
for (let j = 0; j <= b.length; j++) {
|
|
96
|
+
matrix[0][j] = j;
|
|
97
|
+
}
|
|
98
|
+
// Fill in the rest of the matrix
|
|
99
|
+
for (let i = 1; i <= a.length; i++) {
|
|
100
|
+
for (let j = 1; j <= b.length; j++) {
|
|
101
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
102
|
+
matrix[i][j] = Math.min(matrix[i - 1][j] + 1, // deletion
|
|
103
|
+
matrix[i][j - 1] + 1, // insertion
|
|
104
|
+
matrix[i - 1][j - 1] + cost);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return matrix[a.length][b.length];
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Calculate similarity based on Levenshtein distance
|
|
111
|
+
* Returns a value between 0 and 1, where 1 means identical strings
|
|
112
|
+
*/
|
|
113
|
+
export function levenshteinSimilarity(a, b) {
|
|
114
|
+
if (a === b)
|
|
115
|
+
return 1.0;
|
|
116
|
+
if (a.length === 0 || b.length === 0)
|
|
117
|
+
return 0.0;
|
|
118
|
+
const distance = levenshteinDistance(a, b);
|
|
119
|
+
const maxLength = Math.max(a.length, b.length);
|
|
120
|
+
return 1 - distance / maxLength;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Create a similarity calculator function with a specific algorithm
|
|
124
|
+
*/
|
|
125
|
+
export function createSimilarityCalculator(algorithm = "gestalt") {
|
|
126
|
+
return (a, b) => calculateSimilarity(a, b, algorithm);
|
|
127
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "howlongtobeat-core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeScript library to retrieve game completion times from HowLongToBeat.com",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"howlongtobeat",
|
|
7
|
+
"hltb",
|
|
8
|
+
"games",
|
|
9
|
+
"gaming",
|
|
10
|
+
"playtime",
|
|
11
|
+
"game-time",
|
|
12
|
+
"completion-time",
|
|
13
|
+
"api"
|
|
14
|
+
],
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/tapanmeena/howlongtobeat-core.git"
|
|
18
|
+
},
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/tapanmeena/howlongtobeat-core/issues"
|
|
22
|
+
},
|
|
23
|
+
"main": "./script/mod.js",
|
|
24
|
+
"module": "./esm/mod.js",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"import": "./esm/mod.js",
|
|
28
|
+
"require": "./script/mod.js"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^20.9.0"
|
|
36
|
+
},
|
|
37
|
+
"_generatedBy": "dnt@dev"
|
|
38
|
+
}
|
package/script/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
|
|
@@ -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/script/mod.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* HowLongToBeat API for Deno/Node.js
|
|
4
|
+
*
|
|
5
|
+
* A TypeScript wrapper for the HowLongToBeat.com website.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { HowLongToBeat, SearchModifiers } from "@tapan/howlongtobeat";
|
|
10
|
+
*
|
|
11
|
+
* const hltb = new HowLongToBeat({ minimumSimilarity: 0.5 });
|
|
12
|
+
*
|
|
13
|
+
* // Search by name
|
|
14
|
+
* const results = await hltb.search("Elden Ring");
|
|
15
|
+
* console.log(results?.[0]?.mainStory); // e.g., 50.5 hours
|
|
16
|
+
*
|
|
17
|
+
* // Search by ID
|
|
18
|
+
* const game = await hltb.searchById(10270);
|
|
19
|
+
* console.log(game?.gameName); // "Elden Ring"
|
|
20
|
+
*
|
|
21
|
+
* // Filter DLC
|
|
22
|
+
* const dlcOnly = await hltb.search("Dark Souls", SearchModifiers.ISOLATE_DLC);
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @module
|
|
26
|
+
*/
|
|
27
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
28
|
+
exports.levenshteinSimilarity = exports.gestaltSimilarity = exports.calculateSimilarity = exports.SearchModifiers = exports.HowLongToBeat = void 0;
|
|
29
|
+
// Main class
|
|
30
|
+
var HowLongToBeat_js_1 = require("./src/HowLongToBeat.js");
|
|
31
|
+
Object.defineProperty(exports, "HowLongToBeat", { enumerable: true, get: function () { return HowLongToBeat_js_1.HowLongToBeat; } });
|
|
32
|
+
// Types and enums
|
|
33
|
+
var types_js_1 = require("./src/types.js");
|
|
34
|
+
Object.defineProperty(exports, "SearchModifiers", { enumerable: true, get: function () { return types_js_1.SearchModifiers; } });
|
|
35
|
+
// Utility exports for advanced usage
|
|
36
|
+
var similarity_js_1 = require("./src/utils/similarity.js");
|
|
37
|
+
Object.defineProperty(exports, "calculateSimilarity", { enumerable: true, get: function () { return similarity_js_1.calculateSimilarity; } });
|
|
38
|
+
Object.defineProperty(exports, "gestaltSimilarity", { enumerable: true, get: function () { return similarity_js_1.gestaltSimilarity; } });
|
|
39
|
+
Object.defineProperty(exports, "levenshteinSimilarity", { enumerable: true, get: function () { return similarity_js_1.levenshteinSimilarity; } });
|
|
@@ -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,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Main HowLongToBeat class
|
|
4
|
+
*
|
|
5
|
+
* Provides methods to search for games on HowLongToBeat.com
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.HowLongToBeat = void 0;
|
|
9
|
+
const types_js_1 = require("./types.js");
|
|
10
|
+
const client_js_1 = require("./http/client.js");
|
|
11
|
+
const json_js_1 = require("./parser/json.js");
|
|
12
|
+
const similarity_js_1 = require("./utils/similarity.js");
|
|
13
|
+
/**
|
|
14
|
+
* Default options for HowLongToBeat
|
|
15
|
+
*/
|
|
16
|
+
const DEFAULT_OPTIONS = {
|
|
17
|
+
minimumSimilarity: 0.4,
|
|
18
|
+
autoFilterTimes: false,
|
|
19
|
+
similarityAlgorithm: "gestalt",
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* HowLongToBeat API wrapper
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const hltb = new HowLongToBeat();
|
|
27
|
+
* const results = await hltb.search("The Witcher 3");
|
|
28
|
+
* console.log(results);
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
class HowLongToBeat {
|
|
32
|
+
/**
|
|
33
|
+
* Create a new HowLongToBeat instance
|
|
34
|
+
*
|
|
35
|
+
* @param options - Configuration options
|
|
36
|
+
*/
|
|
37
|
+
constructor(options = {}) {
|
|
38
|
+
Object.defineProperty(this, "minimumSimilarity", {
|
|
39
|
+
enumerable: true,
|
|
40
|
+
configurable: true,
|
|
41
|
+
writable: true,
|
|
42
|
+
value: void 0
|
|
43
|
+
});
|
|
44
|
+
Object.defineProperty(this, "autoFilterTimes", {
|
|
45
|
+
enumerable: true,
|
|
46
|
+
configurable: true,
|
|
47
|
+
writable: true,
|
|
48
|
+
value: void 0
|
|
49
|
+
});
|
|
50
|
+
Object.defineProperty(this, "similarityAlgorithm", {
|
|
51
|
+
enumerable: true,
|
|
52
|
+
configurable: true,
|
|
53
|
+
writable: true,
|
|
54
|
+
value: void 0
|
|
55
|
+
});
|
|
56
|
+
Object.defineProperty(this, "similarityCalculator", {
|
|
57
|
+
enumerable: true,
|
|
58
|
+
configurable: true,
|
|
59
|
+
writable: true,
|
|
60
|
+
value: void 0
|
|
61
|
+
});
|
|
62
|
+
this.minimumSimilarity = options.minimumSimilarity ??
|
|
63
|
+
DEFAULT_OPTIONS.minimumSimilarity;
|
|
64
|
+
this.autoFilterTimes = options.autoFilterTimes ??
|
|
65
|
+
DEFAULT_OPTIONS.autoFilterTimes;
|
|
66
|
+
this.similarityAlgorithm = options.similarityAlgorithm ??
|
|
67
|
+
DEFAULT_OPTIONS.similarityAlgorithm;
|
|
68
|
+
this.similarityCalculator = (0, similarity_js_1.createSimilarityCalculator)(this.similarityAlgorithm);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Search for games by name
|
|
72
|
+
*
|
|
73
|
+
* @param gameName - The name of the game to search for
|
|
74
|
+
* @param modifier - Optional search modifier to filter results
|
|
75
|
+
* @returns Array of matching games sorted by similarity, or null on error
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* const hltb = new HowLongToBeat();
|
|
80
|
+
* const results = await hltb.search("Elden Ring");
|
|
81
|
+
* if (results && results.length > 0) {
|
|
82
|
+
* console.log(`Main story: ${results[0].mainStory} hours`);
|
|
83
|
+
* }
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
async search(gameName, modifier = types_js_1.SearchModifiers.NONE) {
|
|
87
|
+
if (!gameName || gameName.trim().length === 0) {
|
|
88
|
+
// TODO: implement proper error handling
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const response = await (0, client_js_1.executeSearch)(gameName.trim(), modifier);
|
|
92
|
+
if (!response || !response.data) {
|
|
93
|
+
// TODO: implement proper error handling
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const entries = (0, json_js_1.parseGameEntries)(response.data, gameName, this.autoFilterTimes, this.similarityCalculator);
|
|
97
|
+
return (0, json_js_1.filterBySimilarity)(entries, this.minimumSimilarity);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Search for a game by its HLTB ID
|
|
101
|
+
*
|
|
102
|
+
* @param gameId - The unique game ID on HowLongToBeat
|
|
103
|
+
* @returns The matching game entry, or null if not found
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```typescript
|
|
107
|
+
* const hltb = new HowLongToBeat();
|
|
108
|
+
* const game = await hltb.searchById(10270); // Elden Ring
|
|
109
|
+
* if (game) {
|
|
110
|
+
* console.log(`${game.gameName}: ${game.mainStory} hours`);
|
|
111
|
+
* }
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
async searchById(gameId) {
|
|
115
|
+
if (!gameId || gameId <= 0) {
|
|
116
|
+
// TODO: implement proper error handling
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
// First, fetch the game page to get the title
|
|
120
|
+
const html = await (0, client_js_1.fetchGamePage)(gameId);
|
|
121
|
+
if (!html) {
|
|
122
|
+
// TODO: implement proper error handling
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
const gameTitle = (0, client_js_1.extractGameTitle)(html);
|
|
126
|
+
if (!gameTitle) {
|
|
127
|
+
// TODO: implement proper error handling
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
// Search using the extracted title
|
|
131
|
+
const response = await (0, client_js_1.executeSearch)(gameTitle);
|
|
132
|
+
if (!response || !response.data) {
|
|
133
|
+
// TODO: implement proper error handling
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
// Find the exact game by ID
|
|
137
|
+
const matchingGame = response.data.find((game) => game.game_id === gameId);
|
|
138
|
+
if (!matchingGame) {
|
|
139
|
+
// TODO: implement proper error handling
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
const entries = (0, json_js_1.parseGameEntries)([matchingGame], gameTitle, this.autoFilterTimes, this.similarityCalculator);
|
|
143
|
+
return entries.length > 0 ? entries[0] : null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
exports.HowLongToBeat = HowLongToBeat;
|
|
@@ -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"}
|