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.
Files changed (61) hide show
  1. package/.github/workflows/npm-publish.yml +22 -0
  2. package/README.md +3 -1
  3. package/dist/authentication/halo-authentication-client.d.ts +4 -1
  4. package/dist/authentication/halo-authentication-client.js +7 -2
  5. package/dist/authentication/halo-authentication-client.js.map +1 -1
  6. package/dist/authentication/xbox-authentication-client.d.ts +5 -1
  7. package/dist/authentication/xbox-authentication-client.js +20 -16
  8. package/dist/authentication/xbox-authentication-client.js.map +1 -1
  9. package/dist/core/halo-infinite-client.d.ts +9 -1
  10. package/dist/core/halo-infinite-client.js +15 -2
  11. package/dist/core/halo-infinite-client.js.map +1 -1
  12. package/dist/index.d.ts +5 -0
  13. package/dist/index.js +2 -0
  14. package/dist/index.js.map +1 -1
  15. package/dist/models/halo-infinite/game-variant-category.d.ts +11 -0
  16. package/dist/models/halo-infinite/game-variant-category.js +13 -0
  17. package/dist/models/halo-infinite/game-variant-category.js.map +1 -0
  18. package/dist/models/halo-infinite/match-info.d.ts +36 -0
  19. package/dist/models/halo-infinite/match-info.js +2 -0
  20. package/dist/models/halo-infinite/match-info.js.map +1 -0
  21. package/dist/models/halo-infinite/match-outcome.d.ts +4 -0
  22. package/dist/models/halo-infinite/match-outcome.js +6 -0
  23. package/dist/models/halo-infinite/match-outcome.js.map +1 -0
  24. package/dist/models/halo-infinite/match-skill.d.ts +46 -0
  25. package/dist/models/halo-infinite/match-skill.js +2 -0
  26. package/dist/models/halo-infinite/match-skill.js.map +1 -0
  27. package/dist/models/halo-infinite/match-stats.d.ts +32 -0
  28. package/dist/models/halo-infinite/match-stats.js +2 -0
  29. package/dist/models/halo-infinite/match-stats.js.map +1 -0
  30. package/dist/models/halo-infinite/match-type.d.ts +6 -0
  31. package/dist/models/halo-infinite/match-type.js +8 -0
  32. package/dist/models/halo-infinite/match-type.js.map +1 -0
  33. package/dist/models/halo-infinite/player-match-history.d.ts +10 -0
  34. package/dist/models/halo-infinite/player-match-history.js +2 -0
  35. package/dist/models/halo-infinite/player-match-history.js.map +1 -0
  36. package/dist/models/halo-infinite/playlist-experience.d.ts +4 -0
  37. package/dist/models/halo-infinite/playlist-experience.js +6 -0
  38. package/dist/models/halo-infinite/playlist-experience.js.map +1 -0
  39. package/dist/models/halo-infinite/stats.d.ts +90 -0
  40. package/dist/models/halo-infinite/stats.js +2 -0
  41. package/dist/models/halo-infinite/stats.js.map +1 -0
  42. package/dist/util/date-time.d.ts +2 -0
  43. package/dist/util/date-time.js +14 -0
  44. package/dist/util/date-time.js.map +1 -0
  45. package/package.json +6 -3
  46. package/src/authentication/halo-authentication-client.ts +14 -6
  47. package/src/authentication/xbox-authentication-client.ts +26 -21
  48. package/src/core/halo-infinite-client.ts +60 -2
  49. package/src/index.ts +5 -0
  50. package/src/models/halo-infinite/game-variant-category.ts +11 -0
  51. package/src/models/halo-infinite/match-info.ts +38 -0
  52. package/src/models/halo-infinite/match-outcome.ts +4 -0
  53. package/src/models/halo-infinite/match-skill.ts +48 -0
  54. package/src/models/halo-infinite/match-stats.ts +35 -0
  55. package/src/models/halo-infinite/match-type.ts +6 -0
  56. package/src/models/halo-infinite/player-match-history.ts +11 -0
  57. package/src/models/halo-infinite/playlist-experience.ts +4 -0
  58. package/src/models/halo-infinite/stats.ts +85 -0
  59. package/src/util/date-time.ts +12 -0
  60. package/tsconfig.app.json +1 -1
  61. 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,2 @@
1
+ import { GameVariantCategory } from "./game-variant-category";
2
+ //# sourceMappingURL=stats.js.map
@@ -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,2 @@
1
+ import { DateTime } from "luxon";
2
+ export declare function coalesceDateTime(maybeDateTime: unknown): DateTime | undefined;
@@ -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
- "version": "1.0.2",
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
- "oauth-pkce": "^0.0.6",
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, { AxiosInstance } from "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 { DateTime } from "luxon";
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<Token | null>,
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 currentToken = await this.loadToken();
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 getPkce from "oauth-pkce";
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<XboxAuthenticationToken | null>,
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 new Promise<{
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 currentToken = await this.loadToken();
96
-
97
- if (currentToken && currentToken.expiresAt > DateTime.now()) {
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 { verifier, challenge } = await this.getPkce();
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: 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: 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 executeArrayRequest<T>(
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.executeArrayRequest<PlaylistCsrContainer>(
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,4 @@
1
+ export enum MatchOutcome {
2
+ Win = 2,
3
+ Loss = 3,
4
+ }
@@ -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,6 @@
1
+ export enum MatchType {
2
+ All = 0,
3
+ Matchmaking = 1,
4
+ Custom = 2,
5
+ Local = 3,
6
+ }
@@ -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,4 @@
1
+ export enum PlaylistExperience {
2
+ Arena = 2,
3
+ Featured = 5,
4
+ }
@@ -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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "extends": "./tsconfig.json",
3
3
  "compilerOptions": {
4
- "rootDir": "src",
4
+ "rootDir": "./src",
5
5
  "moduleResolution": "node",
6
6
  "outDir": "dist",
7
7
  "declarationDir": "dist",
package/tsconfig.json CHANGED
@@ -25,9 +25,6 @@
25
25
  {
26
26
  "path": "./tsconfig.app.json"
27
27
  },
28
- {
29
- "path": "./tsconfig.build.json"
30
- },
31
28
  {
32
29
  "path": "./tsconfig.spec.json"
33
30
  }