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,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
+ }
@@ -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,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }
@@ -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"}