halo-infinite-api 1.0.2 → 1.0.4
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/.github/workflows/npm-publish.yml +22 -0
- package/README.md +3 -1
- package/dist/authentication/halo-authentication-client.d.ts +4 -1
- package/dist/authentication/halo-authentication-client.js +7 -2
- package/dist/authentication/halo-authentication-client.js.map +1 -1
- package/dist/authentication/xbox-authentication-client.d.ts +5 -1
- package/dist/authentication/xbox-authentication-client.js +20 -16
- package/dist/authentication/xbox-authentication-client.js.map +1 -1
- package/dist/core/halo-infinite-client.d.ts +9 -1
- package/dist/core/halo-infinite-client.js +15 -2
- package/dist/core/halo-infinite-client.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/models/halo-infinite/game-variant-category.d.ts +11 -0
- package/dist/models/halo-infinite/game-variant-category.js +13 -0
- package/dist/models/halo-infinite/game-variant-category.js.map +1 -0
- package/dist/models/halo-infinite/match-info.d.ts +36 -0
- package/dist/models/halo-infinite/match-info.js +2 -0
- package/dist/models/halo-infinite/match-info.js.map +1 -0
- package/dist/models/halo-infinite/match-outcome.d.ts +4 -0
- package/dist/models/halo-infinite/match-outcome.js +6 -0
- package/dist/models/halo-infinite/match-outcome.js.map +1 -0
- package/dist/models/halo-infinite/match-skill.d.ts +46 -0
- package/dist/models/halo-infinite/match-skill.js +2 -0
- package/dist/models/halo-infinite/match-skill.js.map +1 -0
- package/dist/models/halo-infinite/match-stats.d.ts +32 -0
- package/dist/models/halo-infinite/match-stats.js +2 -0
- package/dist/models/halo-infinite/match-stats.js.map +1 -0
- package/dist/models/halo-infinite/match-type.d.ts +6 -0
- package/dist/models/halo-infinite/match-type.js +8 -0
- package/dist/models/halo-infinite/match-type.js.map +1 -0
- package/dist/models/halo-infinite/player-match-history.d.ts +10 -0
- package/dist/models/halo-infinite/player-match-history.js +2 -0
- package/dist/models/halo-infinite/player-match-history.js.map +1 -0
- package/dist/models/halo-infinite/playlist-experience.d.ts +4 -0
- package/dist/models/halo-infinite/playlist-experience.js +6 -0
- package/dist/models/halo-infinite/playlist-experience.js.map +1 -0
- package/dist/models/halo-infinite/stats.d.ts +90 -0
- package/dist/models/halo-infinite/stats.js +2 -0
- package/dist/models/halo-infinite/stats.js.map +1 -0
- package/dist/util/date-time.d.ts +2 -0
- package/dist/util/date-time.js +14 -0
- package/dist/util/date-time.js.map +1 -0
- package/package.json +6 -3
- package/src/authentication/halo-authentication-client.ts +14 -6
- package/src/authentication/xbox-authentication-client.ts +26 -21
- package/src/core/halo-infinite-client.ts +60 -2
- package/src/index.ts +5 -0
- package/src/models/halo-infinite/game-variant-category.ts +11 -0
- package/src/models/halo-infinite/match-info.ts +38 -0
- package/src/models/halo-infinite/match-outcome.ts +4 -0
- package/src/models/halo-infinite/match-skill.ts +48 -0
- package/src/models/halo-infinite/match-stats.ts +35 -0
- package/src/models/halo-infinite/match-type.ts +6 -0
- package/src/models/halo-infinite/player-match-history.ts +11 -0
- package/src/models/halo-infinite/playlist-experience.ts +4 -0
- package/src/models/halo-infinite/stats.ts +85 -0
- package/src/util/date-time.ts +12 -0
- package/tsconfig.app.json +1 -1
- package/tsconfig.json +0 -3
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { GameVariantCategory } from "./game-variant-category";
|
|
2
|
+
interface OddballStats {
|
|
3
|
+
KillsAsSkullCarrier: number;
|
|
4
|
+
LongestTimeAsSkullCarrier: string;
|
|
5
|
+
SkullCarriersKilled: number;
|
|
6
|
+
SkullGrabs: number;
|
|
7
|
+
TimeAsSkullCarrier: string;
|
|
8
|
+
SkullScoringTicks: number;
|
|
9
|
+
}
|
|
10
|
+
interface ZonesStats {
|
|
11
|
+
StrongholdCaptures: number;
|
|
12
|
+
StrongholdDefensiveKills: number;
|
|
13
|
+
StrongholdOffensiveKills: number;
|
|
14
|
+
StrongholdSecures: number;
|
|
15
|
+
StrongholdOccupationTime: string;
|
|
16
|
+
StrongholdScoringTicks: number;
|
|
17
|
+
}
|
|
18
|
+
interface CaptureTheFlagStats {
|
|
19
|
+
FlagCaptureAssists: number;
|
|
20
|
+
FlagCaptures: number;
|
|
21
|
+
FlagCarriersKilled: number;
|
|
22
|
+
FlagGrabs: number;
|
|
23
|
+
FlagReturnersKilled: number;
|
|
24
|
+
FlagReturns: number;
|
|
25
|
+
FlagSecures: number;
|
|
26
|
+
FlagSteals: number;
|
|
27
|
+
KillsAsFlagCarrier: number;
|
|
28
|
+
KillsAsFlagReturner: number;
|
|
29
|
+
TimeAsFlagCarrier: string;
|
|
30
|
+
}
|
|
31
|
+
type StatsMap = {
|
|
32
|
+
[GameVariantCategory.MultiplayerOddball]: {
|
|
33
|
+
OddballStats: OddballStats;
|
|
34
|
+
};
|
|
35
|
+
[GameVariantCategory.MultiplayerStrongholds]: {
|
|
36
|
+
ZonesStats: ZonesStats;
|
|
37
|
+
};
|
|
38
|
+
[GameVariantCategory.MultiplayerCtf]: {
|
|
39
|
+
CaptureTheFlagStats: CaptureTheFlagStats;
|
|
40
|
+
};
|
|
41
|
+
[GameVariantCategory.MultiplayerKingOfTheHill]: {
|
|
42
|
+
ZonesStats: ZonesStats;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
export type Stats<TCategory extends GameVariantCategory> = {
|
|
46
|
+
CoreStats: {
|
|
47
|
+
Score: number;
|
|
48
|
+
PersonalScore: number;
|
|
49
|
+
RoundsWon: number;
|
|
50
|
+
RoundsLost: number;
|
|
51
|
+
RoundsTied: number;
|
|
52
|
+
Kills: number;
|
|
53
|
+
Deaths: number;
|
|
54
|
+
Assists: number;
|
|
55
|
+
KDA: number;
|
|
56
|
+
Suicides: number;
|
|
57
|
+
Betrayals: number;
|
|
58
|
+
AverageLifeDuration: string;
|
|
59
|
+
GrenadeKills: number;
|
|
60
|
+
HeadshotKills: number;
|
|
61
|
+
MeleeKills: number;
|
|
62
|
+
PowerWeaponKills: number;
|
|
63
|
+
ShotsFired: number;
|
|
64
|
+
ShotsHit: number;
|
|
65
|
+
Accuracy: number;
|
|
66
|
+
DamageDealt: number;
|
|
67
|
+
DamageTaken: number;
|
|
68
|
+
CalloutAssists: number;
|
|
69
|
+
VehicleDestroys: number;
|
|
70
|
+
DriverAssists: number;
|
|
71
|
+
Hijacks: number;
|
|
72
|
+
EmpAssists: number;
|
|
73
|
+
MaxKillingSpree: number;
|
|
74
|
+
Medals: {
|
|
75
|
+
NameId: number;
|
|
76
|
+
Count: number;
|
|
77
|
+
TotalPersonalScoreAwarded: number;
|
|
78
|
+
}[];
|
|
79
|
+
PersonalScores: {
|
|
80
|
+
NameId: number;
|
|
81
|
+
Count: number;
|
|
82
|
+
TotalPersonalScoreAwarded: number;
|
|
83
|
+
}[];
|
|
84
|
+
DeprecatedDamageDealt: number;
|
|
85
|
+
DeprecatedDamageTaken: number;
|
|
86
|
+
Spawns: number;
|
|
87
|
+
ObjectivesCompleted: number;
|
|
88
|
+
};
|
|
89
|
+
} & (TCategory extends keyof StatsMap ? StatsMap[TCategory] : {});
|
|
90
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.js","sourceRoot":"","sources":["../../../src/models/halo-infinite/stats.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { DateTime } from "luxon";
|
|
2
|
+
export function coalesceDateTime(maybeDateTime) {
|
|
3
|
+
if (DateTime.isDateTime(maybeDateTime)) {
|
|
4
|
+
return maybeDateTime;
|
|
5
|
+
}
|
|
6
|
+
else if (maybeDateTime instanceof Date) {
|
|
7
|
+
return DateTime.fromJSDate(maybeDateTime);
|
|
8
|
+
}
|
|
9
|
+
else if (typeof maybeDateTime === "string") {
|
|
10
|
+
return DateTime.fromISO(maybeDateTime);
|
|
11
|
+
}
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=date-time.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"date-time.js","sourceRoot":"","sources":["../../src/util/date-time.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEjC,MAAM,UAAU,gBAAgB,CAAC,aAAsB;IACrD,IAAI,QAAQ,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE;QACtC,OAAO,aAAa,CAAC;KACtB;SAAM,IAAI,aAAa,YAAY,IAAI,EAAE;QACxC,OAAO,QAAQ,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;KAC3C;SAAM,IAAI,OAAO,aAAa,KAAK,QAAQ,EAAE;QAC5C,OAAO,QAAQ,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;KACxC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "halo-infinite-api",
|
|
3
|
-
"
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "1.0.4",
|
|
4
5
|
"description": "An NPM package for accessing the official Halo Infinite API.",
|
|
5
6
|
"main": "dist/index.js",
|
|
6
7
|
"scripts": {
|
|
7
|
-
"test": "echo \"Error: no test specified\" && exit 1",
|
|
8
8
|
"prepack": "tsc --project tsconfig.app.json"
|
|
9
9
|
},
|
|
10
10
|
"repository": {
|
|
@@ -35,7 +35,10 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"axios": "^1.3.5",
|
|
37
37
|
"luxon": "^3.3.0",
|
|
38
|
-
"
|
|
38
|
+
"pkce-challenge": "^4.0.1",
|
|
39
39
|
"simple-oauth2": "^5.0.0"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"@GravlLift:registry": "https://npm.pkg.github.com"
|
|
40
43
|
}
|
|
41
44
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import axios
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { DateTime } from "luxon";
|
|
2
3
|
import type { SpartanToken } from "../models/spartan-token";
|
|
3
4
|
import type { SpartanTokenRequest } from "../models/spartan-token-request";
|
|
4
|
-
import {
|
|
5
|
+
import { coalesceDateTime } from "../util/date-time";
|
|
5
6
|
|
|
6
7
|
export interface Token {
|
|
7
8
|
token: string;
|
|
@@ -13,7 +14,10 @@ export class HaloAuthenticationClient {
|
|
|
13
14
|
|
|
14
15
|
constructor(
|
|
15
16
|
private readonly fetchXstsToken: () => Promise<string>,
|
|
16
|
-
private readonly loadToken: () => Promise<
|
|
17
|
+
private readonly loadToken: () => Promise<{
|
|
18
|
+
token?: string;
|
|
19
|
+
expiresAt?: unknown;
|
|
20
|
+
} | null>,
|
|
17
21
|
private readonly saveToken: (token: Token) => Promise<void>
|
|
18
22
|
) {}
|
|
19
23
|
|
|
@@ -56,11 +60,15 @@ export class HaloAuthenticationClient {
|
|
|
56
60
|
});
|
|
57
61
|
|
|
58
62
|
try {
|
|
59
|
-
const
|
|
63
|
+
const loadedToken = await this.loadToken();
|
|
64
|
+
const currentToken = {
|
|
65
|
+
token: loadedToken?.token ?? "",
|
|
66
|
+
expiresAt: coalesceDateTime(loadedToken?.expiresAt),
|
|
67
|
+
};
|
|
60
68
|
|
|
61
|
-
if (currentToken && currentToken.expiresAt > DateTime.now()) {
|
|
69
|
+
if (currentToken.expiresAt && currentToken.expiresAt > DateTime.now()) {
|
|
62
70
|
// Current token is valid, return it and alert other callers if applicable
|
|
63
|
-
promiseResolver(currentToken);
|
|
71
|
+
promiseResolver(currentToken as Token);
|
|
64
72
|
return currentToken.token;
|
|
65
73
|
} else {
|
|
66
74
|
const xstsToken = await this.fetchXstsToken();
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import axios, { AxiosInstance } from "axios";
|
|
2
|
-
import
|
|
2
|
+
import pkceChallenge from "pkce-challenge";
|
|
3
3
|
import { DateTime } from "luxon";
|
|
4
4
|
import { XboxTicket } from "../models/xbox-ticket";
|
|
5
|
+
import { coalesceDateTime } from "../util/date-time";
|
|
5
6
|
|
|
6
7
|
const SCOPES = ["Xboxlive.signin", "Xboxlive.offline_access"];
|
|
8
|
+
// polyfill crypto for oauth-pkce
|
|
9
|
+
if (!globalThis.window) globalThis.window = {} as any;
|
|
10
|
+
if (!globalThis.window.crypto) {
|
|
11
|
+
globalThis.window.crypto = (await import("node:crypto"))
|
|
12
|
+
.webcrypto as typeof globalThis.window.crypto;
|
|
13
|
+
}
|
|
7
14
|
|
|
8
15
|
export enum RelyingParty {
|
|
9
16
|
Xbox = "http://xboxlive.com",
|
|
@@ -25,7 +32,11 @@ export class XboxAuthenticationClient {
|
|
|
25
32
|
private readonly clientId: string,
|
|
26
33
|
private readonly redirectUri: string,
|
|
27
34
|
private readonly getAuthCode: (authorizeUrl: string) => Promise<string>,
|
|
28
|
-
private readonly loadToken: () => Promise<
|
|
35
|
+
private readonly loadToken: () => Promise<{
|
|
36
|
+
token?: string;
|
|
37
|
+
expiresAt?: unknown;
|
|
38
|
+
refreshToken?: string;
|
|
39
|
+
} | null>,
|
|
29
40
|
private readonly saveToken: (
|
|
30
41
|
token: XboxAuthenticationToken
|
|
31
42
|
) => Promise<void>
|
|
@@ -34,18 +45,7 @@ export class XboxAuthenticationClient {
|
|
|
34
45
|
}
|
|
35
46
|
|
|
36
47
|
private getPkce() {
|
|
37
|
-
return
|
|
38
|
-
verifier: string;
|
|
39
|
-
challenge: string;
|
|
40
|
-
}>((resolve, reject) => {
|
|
41
|
-
getPkce(43, (err, { verifier, challenge }) => {
|
|
42
|
-
if (err) {
|
|
43
|
-
reject(err);
|
|
44
|
-
} else {
|
|
45
|
-
resolve({ verifier, challenge });
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
});
|
|
48
|
+
return pkceChallenge(43);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
public async getAccessToken() {
|
|
@@ -92,11 +92,16 @@ export class XboxAuthenticationClient {
|
|
|
92
92
|
);
|
|
93
93
|
|
|
94
94
|
try {
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
const loadedToken = await this.loadToken();
|
|
96
|
+
const currentToken = {
|
|
97
|
+
...loadedToken,
|
|
98
|
+
token: loadedToken?.token ?? "",
|
|
99
|
+
expiresAt: coalesceDateTime(loadedToken?.expiresAt),
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
if (currentToken.expiresAt && currentToken.expiresAt > DateTime.now()) {
|
|
98
103
|
// Current token is valid, return it and alert other callers if applicable
|
|
99
|
-
promiseResolver(currentToken);
|
|
104
|
+
promiseResolver(currentToken as XboxAuthenticationToken);
|
|
100
105
|
return currentToken.token;
|
|
101
106
|
} else {
|
|
102
107
|
const newToken = await this.fetchOauth2Token();
|
|
@@ -112,7 +117,7 @@ export class XboxAuthenticationClient {
|
|
|
112
117
|
}
|
|
113
118
|
|
|
114
119
|
private async fetchOauth2Token(): Promise<XboxAuthenticationToken> {
|
|
115
|
-
const {
|
|
120
|
+
const { code_verifier, code_challenge } = await this.getPkce();
|
|
116
121
|
|
|
117
122
|
const authorizeUrl = `https://login.live.com/oauth20_authorize.srf?${new URLSearchParams(
|
|
118
123
|
{
|
|
@@ -121,7 +126,7 @@ export class XboxAuthenticationClient {
|
|
|
121
126
|
redirect_uri: this.redirectUri,
|
|
122
127
|
scope: SCOPES.join(" "),
|
|
123
128
|
code_challenge_method: "S256",
|
|
124
|
-
code_challenge
|
|
129
|
+
code_challenge,
|
|
125
130
|
}
|
|
126
131
|
)}`;
|
|
127
132
|
|
|
@@ -143,7 +148,7 @@ export class XboxAuthenticationClient {
|
|
|
143
148
|
scope: SCOPES.join(" "),
|
|
144
149
|
redirect_uri: this.redirectUri,
|
|
145
150
|
client_id: this.clientId,
|
|
146
|
-
code_verifier
|
|
151
|
+
code_verifier,
|
|
147
152
|
}),
|
|
148
153
|
{
|
|
149
154
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
@@ -12,6 +12,10 @@ import {
|
|
|
12
12
|
} from "../authentication/xbox-authentication-client";
|
|
13
13
|
import { HaloAuthenticationClient } from "../authentication/halo-authentication-client";
|
|
14
14
|
import { Playlist } from "../models/halo-infinite/playlist";
|
|
15
|
+
import { MatchType } from "../models/halo-infinite/match-type";
|
|
16
|
+
import { MatchStats } from "src/models/halo-infinite/match-stats";
|
|
17
|
+
import { PlayerMatchHistory } from "src/models/halo-infinite/player-match-history";
|
|
18
|
+
import { MatchSkill } from "src/models/halo-infinite/match-skill";
|
|
15
19
|
|
|
16
20
|
interface ResultContainer<TValue> {
|
|
17
21
|
Id: string;
|
|
@@ -23,6 +27,13 @@ interface ResultsContainer<TValue> {
|
|
|
23
27
|
Value: ResultContainer<TValue>[];
|
|
24
28
|
}
|
|
25
29
|
|
|
30
|
+
interface PaginationContainer<TValue> {
|
|
31
|
+
Start: number;
|
|
32
|
+
Count: number;
|
|
33
|
+
ResultCount: number;
|
|
34
|
+
Results: TValue[];
|
|
35
|
+
}
|
|
36
|
+
|
|
26
37
|
interface TokenPersister {
|
|
27
38
|
load: <T>(tokenName: string) => Promise<T>;
|
|
28
39
|
save: (tokenName: string, token: unknown) => Promise<void>;
|
|
@@ -111,7 +122,7 @@ export class HaloInfiniteClient {
|
|
|
111
122
|
return response.data;
|
|
112
123
|
}
|
|
113
124
|
|
|
114
|
-
private async
|
|
125
|
+
private async executeResultsRequest<T>(
|
|
115
126
|
...args: Parameters<HaloInfiniteClient["executeRequest"]>
|
|
116
127
|
) {
|
|
117
128
|
const result = await this.executeRequest<ResultsContainer<T>>(...args);
|
|
@@ -119,12 +130,31 @@ export class HaloInfiniteClient {
|
|
|
119
130
|
return result.Value;
|
|
120
131
|
}
|
|
121
132
|
|
|
133
|
+
private async executePaginationRequest<T>(
|
|
134
|
+
count: number,
|
|
135
|
+
start: number,
|
|
136
|
+
queryParameters: Record<string, string>,
|
|
137
|
+
...args: Parameters<HaloInfiniteClient["executeRequest"]>
|
|
138
|
+
) {
|
|
139
|
+
const [url, ...rest] = args;
|
|
140
|
+
const result = await this.executeRequest<PaginationContainer<T>>(
|
|
141
|
+
`${url}?${new URLSearchParams({
|
|
142
|
+
...queryParameters,
|
|
143
|
+
count: count.toString(),
|
|
144
|
+
start: start.toString(),
|
|
145
|
+
})}`,
|
|
146
|
+
...rest
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
return result.Results;
|
|
150
|
+
}
|
|
151
|
+
|
|
122
152
|
/** Gets playlist Competitive Skill Rank (CSR) for a player or a set of players.
|
|
123
153
|
* @param playlistId - Unique ID for the playlist.
|
|
124
154
|
* @param playerIds - Array of player xuids.
|
|
125
155
|
*/
|
|
126
156
|
public getPlaylistCsr = (playlistId: string, playerIds: string[]) =>
|
|
127
|
-
this.
|
|
157
|
+
this.executeResultsRequest<PlaylistCsrContainer>(
|
|
128
158
|
`https://${HaloCoreEndpoints.SkillOrigin}.${
|
|
129
159
|
HaloCoreEndpoints.ServiceDomain
|
|
130
160
|
}/hi/playlist/${playlistId}/csrs?players=xuid(${playerIds.join(
|
|
@@ -159,4 +189,32 @@ export class HaloInfiniteClient {
|
|
|
159
189
|
`https://${HaloCoreEndpoints.GameCmsOrigin}.${HaloCoreEndpoints.ServiceDomain}/hi/multiplayer/file/playlists/assets/${playlistId}.json`,
|
|
160
190
|
"get"
|
|
161
191
|
);
|
|
192
|
+
|
|
193
|
+
public getPlayerMatches = (
|
|
194
|
+
playerXuid: string,
|
|
195
|
+
type: MatchType = MatchType.All,
|
|
196
|
+
count: number = 25,
|
|
197
|
+
start: number = 0
|
|
198
|
+
) =>
|
|
199
|
+
this.executePaginationRequest<PlayerMatchHistory>(
|
|
200
|
+
count,
|
|
201
|
+
start,
|
|
202
|
+
{ type: type.toString() },
|
|
203
|
+
`https://${HaloCoreEndpoints.StatsOrigin}.${HaloCoreEndpoints.ServiceDomain}/hi/players/xuid(${playerXuid})/matches`,
|
|
204
|
+
"get"
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
public getMatchStats = (matchId: string) =>
|
|
208
|
+
this.executeRequest<MatchStats>(
|
|
209
|
+
`https://${HaloCoreEndpoints.StatsOrigin}.${HaloCoreEndpoints.ServiceDomain}/hi/matches/${matchId}/stats`,
|
|
210
|
+
"get"
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
public getMatchSkill = (matchId: string, playerIds: string[]) =>
|
|
214
|
+
this.executeResultsRequest<MatchSkill>(
|
|
215
|
+
`https://${HaloCoreEndpoints.SkillOrigin}.${
|
|
216
|
+
HaloCoreEndpoints.ServiceDomain
|
|
217
|
+
}/hi/matches/${matchId}/skill?players=xuid(${playerIds.join("),xuid(")})`,
|
|
218
|
+
"get"
|
|
219
|
+
);
|
|
162
220
|
}
|
package/src/index.ts
CHANGED
|
@@ -3,3 +3,8 @@ export { Playlist } from "./models/halo-infinite/playlist";
|
|
|
3
3
|
export { PlaylistCsrContainer } from "./models/halo-infinite/playlist-csr-container";
|
|
4
4
|
export { UserInfo } from "./models/halo-infinite/user-info";
|
|
5
5
|
export { ServiceRecord } from "./models/halo-infinite/service-record";
|
|
6
|
+
export { MatchType } from "./models/halo-infinite/match-type";
|
|
7
|
+
export { GameVariantCategory } from "./models/halo-infinite/game-variant-category";
|
|
8
|
+
export { MatchStats } from "./models/halo-infinite/match-stats";
|
|
9
|
+
export { PlayerMatchHistory } from "./models/halo-infinite/player-match-history";
|
|
10
|
+
export { Stats } from "./models/halo-infinite/stats";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export enum GameVariantCategory {
|
|
2
|
+
MultiplayerSlayer = 6,
|
|
3
|
+
MultiplayerAttrition = 7,
|
|
4
|
+
MultiplayerFiesta = 9,
|
|
5
|
+
MultiplayerStrongholds = 11,
|
|
6
|
+
MultiplayerKingOfTheHill = 12,
|
|
7
|
+
MultiplayerCtf = 15,
|
|
8
|
+
MultiplayerOddball = 18,
|
|
9
|
+
MultiplayerGrifball = 25,
|
|
10
|
+
MultiplayerLandGrab = 39,
|
|
11
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { GameVariantCategory } from "./game-variant-category";
|
|
2
|
+
import { PlaylistExperience } from "./playlist-experience";
|
|
3
|
+
export interface MatchInfo<
|
|
4
|
+
TCategory extends GameVariantCategory = GameVariantCategory
|
|
5
|
+
> {
|
|
6
|
+
StartTime: string;
|
|
7
|
+
EndTime: string;
|
|
8
|
+
Duration: string;
|
|
9
|
+
LifecycleMode: number;
|
|
10
|
+
GameVariantCategory: TCategory;
|
|
11
|
+
LevelId: string;
|
|
12
|
+
MapVariant: {
|
|
13
|
+
AssetKind: number;
|
|
14
|
+
AssetId: string;
|
|
15
|
+
VersionId: string;
|
|
16
|
+
};
|
|
17
|
+
UgcGameVariant: {
|
|
18
|
+
AssetKind: number;
|
|
19
|
+
AssetId: string;
|
|
20
|
+
VersionId: string;
|
|
21
|
+
};
|
|
22
|
+
ClearanceId: string;
|
|
23
|
+
Playlist: {
|
|
24
|
+
AssetKind: number;
|
|
25
|
+
AssetId: string;
|
|
26
|
+
VersionId: string;
|
|
27
|
+
};
|
|
28
|
+
PlaylistExperience: PlaylistExperience;
|
|
29
|
+
PlaylistMapModePair: {
|
|
30
|
+
AssetKind: number;
|
|
31
|
+
AssetId: string;
|
|
32
|
+
VersionId: string;
|
|
33
|
+
};
|
|
34
|
+
SeasonId: string;
|
|
35
|
+
PlayableDuration: string;
|
|
36
|
+
TeamsEnabled: boolean;
|
|
37
|
+
TeamScoringEnabled: boolean;
|
|
38
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
interface CsrObject {
|
|
2
|
+
Value: number;
|
|
3
|
+
MeasurementMatchesRemaining: number;
|
|
4
|
+
Tier: string;
|
|
5
|
+
TierStart: number;
|
|
6
|
+
NextTier: string;
|
|
7
|
+
NextTierStart: number;
|
|
8
|
+
NextSubTier: number;
|
|
9
|
+
InitialMeasurementMatches: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface StatPerformance {
|
|
13
|
+
Count: number;
|
|
14
|
+
Expected: number;
|
|
15
|
+
StdDev: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface Counterfactual {
|
|
19
|
+
Kills: number;
|
|
20
|
+
Deaths: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface MatchSkill {
|
|
24
|
+
TeamId: number;
|
|
25
|
+
TeamMmr: number;
|
|
26
|
+
TeamMmrs: {
|
|
27
|
+
[key: number]: number;
|
|
28
|
+
};
|
|
29
|
+
RankRecap: {
|
|
30
|
+
PreMatchCsr: CsrObject;
|
|
31
|
+
PostMatchCsr: CsrObject;
|
|
32
|
+
};
|
|
33
|
+
StatPerformances: {
|
|
34
|
+
Kills: StatPerformance;
|
|
35
|
+
Deaths: StatPerformance;
|
|
36
|
+
};
|
|
37
|
+
Counterfactuals: {
|
|
38
|
+
SelfCounterfactual: Counterfactual;
|
|
39
|
+
TierCounterfactuals: {
|
|
40
|
+
Bronze: Counterfactual;
|
|
41
|
+
Silver: Counterfactual;
|
|
42
|
+
Gold: Counterfactual;
|
|
43
|
+
Platinum: Counterfactual;
|
|
44
|
+
Diamond: Counterfactual;
|
|
45
|
+
Onyx: Counterfactual;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { GameVariantCategory } from "./game-variant-category";
|
|
2
|
+
import { MatchInfo } from "./match-info";
|
|
3
|
+
import { Stats } from "./stats";
|
|
4
|
+
|
|
5
|
+
export interface MatchStats<
|
|
6
|
+
TCategory extends GameVariantCategory = GameVariantCategory
|
|
7
|
+
> {
|
|
8
|
+
MatchId: string;
|
|
9
|
+
MatchInfo: MatchInfo<TCategory>;
|
|
10
|
+
Teams: {
|
|
11
|
+
TeamId: number;
|
|
12
|
+
Outcome: number;
|
|
13
|
+
Rank: number;
|
|
14
|
+
Stats: Stats<TCategory>;
|
|
15
|
+
}[];
|
|
16
|
+
Players: {
|
|
17
|
+
PlayerId: string;
|
|
18
|
+
LastTeamId: number;
|
|
19
|
+
Rank: number;
|
|
20
|
+
ParticipationInfo: {
|
|
21
|
+
FirstJoinedTime: string;
|
|
22
|
+
LastLeaveTime: string | null;
|
|
23
|
+
PresentAtBeginning: boolean;
|
|
24
|
+
JoinedInProgress: boolean;
|
|
25
|
+
LeftInProgress: boolean;
|
|
26
|
+
PresentAtCompletion: boolean;
|
|
27
|
+
TimePlayed: string;
|
|
28
|
+
ConfirmedParticipation: boolean | null;
|
|
29
|
+
};
|
|
30
|
+
PlayerTeamStats: {
|
|
31
|
+
TeamId: number;
|
|
32
|
+
Stats: Stats<TCategory>;
|
|
33
|
+
}[];
|
|
34
|
+
}[];
|
|
35
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { MatchInfo } from "./match-info";
|
|
2
|
+
import { MatchOutcome } from "./match-outcome";
|
|
3
|
+
|
|
4
|
+
export interface PlayerMatchHistory {
|
|
5
|
+
MatchId: string;
|
|
6
|
+
LastTeamId: number;
|
|
7
|
+
Outcome: MatchOutcome;
|
|
8
|
+
Rank: number;
|
|
9
|
+
PresentAtEndOfMatch: boolean;
|
|
10
|
+
MatchInfo: MatchInfo;
|
|
11
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { GameVariantCategory } from "./game-variant-category";
|
|
2
|
+
|
|
3
|
+
interface OddballStats {
|
|
4
|
+
KillsAsSkullCarrier: number;
|
|
5
|
+
LongestTimeAsSkullCarrier: string;
|
|
6
|
+
SkullCarriersKilled: number;
|
|
7
|
+
SkullGrabs: number;
|
|
8
|
+
TimeAsSkullCarrier: string;
|
|
9
|
+
SkullScoringTicks: number;
|
|
10
|
+
}
|
|
11
|
+
interface ZonesStats {
|
|
12
|
+
StrongholdCaptures: number;
|
|
13
|
+
StrongholdDefensiveKills: number;
|
|
14
|
+
StrongholdOffensiveKills: number;
|
|
15
|
+
StrongholdSecures: number;
|
|
16
|
+
StrongholdOccupationTime: string;
|
|
17
|
+
StrongholdScoringTicks: number;
|
|
18
|
+
}
|
|
19
|
+
interface CaptureTheFlagStats {
|
|
20
|
+
FlagCaptureAssists: number;
|
|
21
|
+
FlagCaptures: number;
|
|
22
|
+
FlagCarriersKilled: number;
|
|
23
|
+
FlagGrabs: number;
|
|
24
|
+
FlagReturnersKilled: number;
|
|
25
|
+
FlagReturns: number;
|
|
26
|
+
FlagSecures: number;
|
|
27
|
+
FlagSteals: number;
|
|
28
|
+
KillsAsFlagCarrier: number;
|
|
29
|
+
KillsAsFlagReturner: number;
|
|
30
|
+
TimeAsFlagCarrier: string;
|
|
31
|
+
}
|
|
32
|
+
type StatsMap = {
|
|
33
|
+
[GameVariantCategory.MultiplayerOddball]: { OddballStats: OddballStats };
|
|
34
|
+
[GameVariantCategory.MultiplayerStrongholds]: { ZonesStats: ZonesStats };
|
|
35
|
+
[GameVariantCategory.MultiplayerCtf]: {
|
|
36
|
+
CaptureTheFlagStats: CaptureTheFlagStats;
|
|
37
|
+
};
|
|
38
|
+
[GameVariantCategory.MultiplayerKingOfTheHill]: { ZonesStats: ZonesStats };
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type Stats<TCategory extends GameVariantCategory> = {
|
|
42
|
+
CoreStats: {
|
|
43
|
+
Score: number;
|
|
44
|
+
PersonalScore: number;
|
|
45
|
+
RoundsWon: number;
|
|
46
|
+
RoundsLost: number;
|
|
47
|
+
RoundsTied: number;
|
|
48
|
+
Kills: number;
|
|
49
|
+
Deaths: number;
|
|
50
|
+
Assists: number;
|
|
51
|
+
KDA: number;
|
|
52
|
+
Suicides: number;
|
|
53
|
+
Betrayals: number;
|
|
54
|
+
AverageLifeDuration: string;
|
|
55
|
+
GrenadeKills: number;
|
|
56
|
+
HeadshotKills: number;
|
|
57
|
+
MeleeKills: number;
|
|
58
|
+
PowerWeaponKills: number;
|
|
59
|
+
ShotsFired: number;
|
|
60
|
+
ShotsHit: number;
|
|
61
|
+
Accuracy: number;
|
|
62
|
+
DamageDealt: number;
|
|
63
|
+
DamageTaken: number;
|
|
64
|
+
CalloutAssists: number;
|
|
65
|
+
VehicleDestroys: number;
|
|
66
|
+
DriverAssists: number;
|
|
67
|
+
Hijacks: number;
|
|
68
|
+
EmpAssists: number;
|
|
69
|
+
MaxKillingSpree: number;
|
|
70
|
+
Medals: {
|
|
71
|
+
NameId: number;
|
|
72
|
+
Count: number;
|
|
73
|
+
TotalPersonalScoreAwarded: number;
|
|
74
|
+
}[];
|
|
75
|
+
PersonalScores: {
|
|
76
|
+
NameId: number;
|
|
77
|
+
Count: number;
|
|
78
|
+
TotalPersonalScoreAwarded: number;
|
|
79
|
+
}[];
|
|
80
|
+
DeprecatedDamageDealt: number;
|
|
81
|
+
DeprecatedDamageTaken: number;
|
|
82
|
+
Spawns: number;
|
|
83
|
+
ObjectivesCompleted: number;
|
|
84
|
+
};
|
|
85
|
+
} & (TCategory extends keyof StatsMap ? StatsMap[TCategory] : {});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { DateTime } from "luxon";
|
|
2
|
+
|
|
3
|
+
export function coalesceDateTime(maybeDateTime: unknown) {
|
|
4
|
+
if (DateTime.isDateTime(maybeDateTime)) {
|
|
5
|
+
return maybeDateTime;
|
|
6
|
+
} else if (maybeDateTime instanceof Date) {
|
|
7
|
+
return DateTime.fromJSDate(maybeDateTime);
|
|
8
|
+
} else if (typeof maybeDateTime === "string") {
|
|
9
|
+
return DateTime.fromISO(maybeDateTime);
|
|
10
|
+
}
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
package/tsconfig.app.json
CHANGED