|
import axios, { Method } from 'axios' |
|
|
|
const BaseURL = 'https://api.tracker.gg/api/v1' |
|
const getStat = (stats:any, stat:string) => stats && stats[stat] ? stats[stat].value : 0 |
|
|
|
export namespace Warzone { |
|
export const Initialize = async (platform:string, player:string) => |
|
GenericRequest(Requests.Warzone.Initialize(platform, player)) |
|
export const MatchList = async (platform:string, player:string, next:number=null):Promise<Types.Warzone.Matches> => |
|
GenericRequest(Requests.Warzone.MatchList(platform, player, next)) |
|
export const MatchDetails = async (matchId:string|number):Promise<Types.Warzone.MatchDetails> => |
|
GenericRequest(Requests.Warzone.MatchDetails(matchId)) |
|
} |
|
export namespace Normalize { |
|
export const Match = (match:Types.Warzone.MatchSummary):any => ({ |
|
matchId: match.attributes.id, |
|
mapId: match.attributes.mapId, |
|
modeId: match.attributes.modeId, |
|
utcSecStart: new Date(match.metadata.timestamp).getSeconds(), |
|
utcSecEnd: new Date(match.metadata.timestamp).getSeconds() + match.metadata.duration.value, |
|
}) |
|
export const Teams = (matchDetails:Types.Warzone.MatchDetails) => { |
|
const teams:any = [] |
|
for(const seg of matchDetails.segments) { |
|
let team = teams.find((team:any) => team.name === seg.attributes.team) |
|
if (!team) { |
|
teams.push({ |
|
name: seg.attributes.team, |
|
placement: seg.metadata.placement.value, |
|
time: 0, |
|
players: [], |
|
}) |
|
team = teams[teams.length-1] |
|
} |
|
team.placement = seg.metadata.placement.value |
|
team.players.push({ |
|
username: seg.metadata.platformUserHandle, |
|
clantag: seg.metadata.clanTag, |
|
platform: seg.attributes.platformSlug, |
|
rank: null, // rank information is not available via TRN (rank is currently useless anyway - 05/26/2020) |
|
loadouts: [], // loadout information is not available via TRN |
|
stats: { |
|
kills: getStat(seg.stats, 'kills'), |
|
deaths: getStat(seg.stats, 'deaths'), |
|
score: getStat(seg.stats, 'score'), |
|
assists: getStat(seg.stats, 'assists'), |
|
headshots: getStat(seg.stats, 'headshots'), |
|
executions: getStat(seg.stats, 'executions'), |
|
damageDone: getStat(seg.stats, 'damageDone'), |
|
damageTaken: getStat(seg.stats, 'damageTaken'), |
|
longestStreak: getStat(seg.stats, 'longestStreak'), |
|
timePlayed: getStat(seg.stats, 'timePlayed'), |
|
distanceTraveled: getStat(seg.stats, 'distanceTraveled'), |
|
percentTimeMoving: getStat(seg.stats, 'percentTimeMoving'), |
|
}, |
|
}) |
|
} |
|
return teams |
|
} |
|
export const Performance = (match:Types.Warzone.MatchSummary, player:{ _id:string }) => { |
|
const normalizedPerformance:any = {} |
|
normalizedPerformance.matchId = match.attributes.id |
|
normalizedPerformance.player = { |
|
_id: player._id, |
|
username: match.segments[0].metadata.platformUserHandle, |
|
clantag: match.segments[0].metadata.clanTag, |
|
} |
|
// Count downs |
|
let downs = [] |
|
const downKeys = Object.keys(match.segments[0].stats).filter(key => key.includes('objectiveBrDownEnemyCircle')) |
|
for(const key of downKeys) { |
|
const circleIndex = Number(key.replace('objectiveBrDownEnemyCircle', '')) |
|
downs[circleIndex] = getStat(match.segments[0].stats, key) |
|
} |
|
try { |
|
normalizedPerformance.stats = { |
|
kills: getStat(match.segments[0].stats, 'kills'), |
|
deaths: getStat(match.segments[0].stats, 'deaths'), |
|
downs, |
|
gulagKills: getStat(match.segments[0].stats, 'gulagKills'), |
|
gulagDeaths: getStat(match.segments[0].stats, 'gulagDeaths'), |
|
revives: getStat(match.segments[0].stats, 'objectiveReviver'), |
|
contracts: getStat(match.segments[0].stats, 'objectiveBrMissionPickupTablet'), |
|
teamWipes: getStat(match.segments[0].stats, 'objectiveTeamWiped'), |
|
lootCrates: getStat(match.segments[0].stats, 'objectiveBrCacheOpen'), |
|
buyStations: getStat(match.segments[0].stats, 'objectiveBrKioskBuy'), |
|
teamPlacement: match.segments[0].metadata.placement, |
|
teamSurvivalTime: getStat(match.segments[0].stats, 'teamSurvivalTime'), |
|
xp: { |
|
score: getStat(match.segments[0].stats, 'scoreXp'), |
|
match: getStat(match.segments[0].stats, 'matchXp'), |
|
bonus: getStat(match.segments[0].stats, 'bonusXp'), |
|
medal: getStat(match.segments[0].stats, 'medalXp'), |
|
misc: getStat(match.segments[0].stats, 'miscXp'), |
|
challenge: getStat(match.segments[0].stats, 'challengeXp'), |
|
} |
|
} |
|
} catch(e) { |
|
console.log(` Error parsing performance stats: ${e}`) |
|
} |
|
return normalizedPerformance |
|
} |
|
} |
|
|
|
interface Request { |
|
options: { |
|
method: Method |
|
url: string |
|
} |
|
} |
|
namespace Requests { |
|
export namespace Warzone { |
|
export const Initialize = (platform:string, player:string):Request => ({ |
|
options: { |
|
method: 'GET', |
|
url: `https://cod.tracker.gg/warzone/profile/${platform}/${encodeURIComponent(player)}/matches` |
|
} |
|
}) |
|
export const MatchList = (platform:string, player:string, next:number):Request => ({ |
|
options: { |
|
method: 'GET', |
|
url: `${BaseURL}/warzone/matches/${platform}/${encodeURIComponent(player)}?type=wz&next=${next}` |
|
} |
|
}) |
|
export const MatchDetails = (matchId:string|number):Request => ({ |
|
options: { |
|
method: 'GET', |
|
url: `${BaseURL}/warzone/matches/${matchId}` |
|
} |
|
}) |
|
} |
|
} |
|
|
|
const GenericRequest = async ({ options }:Request) => { |
|
try { |
|
console.log(` Requesting ${options.url}`) |
|
const { data:res, status } = await axios({ |
|
...options, |
|
headers: { |
|
'Cache-Control': 'no-cache' |
|
} |
|
}) |
|
if (status !== 200) { |
|
console.log(res.errors[0].code) |
|
throw res.errors[0].code |
|
} |
|
return res.data |
|
} catch(e) { |
|
throw { code: e.response.data.errors[0].code, status: Number(e.message.replace('Request failed with status code ', '')) } |
|
} |
|
} |
|
|
|
export namespace Types { |
|
// Segments are basically just users |
|
export namespace Segment { |
|
export interface Stat { |
|
rank: number |
|
percentile: number |
|
displayName: string |
|
displayCategory: string |
|
category: string |
|
metdata: {} |
|
value: number |
|
displayValue: string |
|
displayType: string |
|
} |
|
} |
|
export interface Segment { |
|
type: string |
|
attributes: { |
|
platformUserIdentifier: string |
|
platformSlug: string // platform name |
|
team: string |
|
} |
|
metadata: { |
|
platformUserHandle: string // player username |
|
clanTag: string |
|
placement: number | any |
|
} |
|
expiryDate: Date |
|
// Stat interfaces can differ for match details vs match summary |
|
stats: Types.Warzone.MatchDetails.Segment.Stats | Types.Warzone.MatchSummary.Segment.Stats |
|
} |
|
export namespace Warzone { |
|
export interface Matches { |
|
matches: MatchSummary[] |
|
paginationType: number |
|
metadata: { |
|
next: number |
|
} |
|
requestingPlayerAttributes: { |
|
platformUserIdentifier: string |
|
} |
|
} |
|
export interface MatchSummary { |
|
attributes: { |
|
id: string |
|
mapId: string |
|
modeId: string |
|
} |
|
metadata: { |
|
duration: { |
|
value: number |
|
displayValue: string |
|
displayType: string |
|
} |
|
timestamp: string |
|
playerCount: number |
|
teamCount: number |
|
mapName: string |
|
mapImageUrl: string |
|
modeName: string |
|
} |
|
segments: Types.Warzone.MatchSummary.Segment[] |
|
} |
|
export namespace MatchSummary { |
|
export interface Segment extends Types.Segment { |
|
metadata: { |
|
platformUserHandle: string // player username |
|
clanTag: string |
|
placement: number |
|
} |
|
stats: Types.Warzone.MatchSummary.Segment.Stats |
|
} |
|
export namespace Segment { |
|
export interface Stats extends Types.Warzone.MatchDetails.Segment.Stats { |
|
medalXp: Types.Segment.Stat |
|
matchXp: Types.Segment.Stat |
|
scoreXp: Types.Segment.Stat |
|
totalXp: Types.Segment.Stat |
|
miscXp: Types.Segment.Stat |
|
bonusXp: Types.Segment.Stat |
|
challengeXp: Types.Segment.Stat |
|
placement: Types.Segment.Stat |
|
teamPlacement: Types.Segment.Stat |
|
teamSurvivalTime: Types.Segment.Stat |
|
gulagKills?: Types.Segment.Stat |
|
gulagDeaths?: Types.Segment.Stat |
|
objectiveReviver?: Types.Segment.Stat |
|
objectiveTeamWiped?: Types.Segment.Stat |
|
objectiveBrKioskBuy?: Types.Segment.Stat |
|
objectiveBrCacheOpen?: Types.Segment.Stat |
|
objectiveLastStandKill?: Types.Segment.Stat |
|
objectiveBrMissionPickupTablet?: Types.Segment.Stat |
|
objectiveBrDownEnemyCircle1?: Types.Segment.Stat |
|
objectiveBrDownEnemyCircle2?: Types.Segment.Stat |
|
objectiveBrDownEnemyCircle3?: Types.Segment.Stat |
|
objectiveBrDownEnemyCircle4?: Types.Segment.Stat |
|
objectiveBrDownEnemyCircle5?: Types.Segment.Stat |
|
objectiveBrDownEnemyCircle6?: Types.Segment.Stat |
|
objectiveBrDownEnemyCircle7?: Types.Segment.Stat |
|
objectiveBrDownEnemyCircle8?: Types.Segment.Stat |
|
} |
|
} |
|
} |
|
export interface MatchDetails { |
|
attributes: { |
|
id: string |
|
mapId: string |
|
modeId: string |
|
} |
|
metadata: { |
|
duration: { |
|
value: number |
|
displayValue: string |
|
displayType: string |
|
} |
|
timestamp: string |
|
playerCount: number |
|
teamCount: number |
|
mapName: string |
|
mapImageUrl: string |
|
modeName: string |
|
} |
|
segments: Types.Warzone.MatchDetails.Segment[] |
|
} |
|
export namespace MatchDetails { |
|
export interface Segment extends Types.Segment { |
|
metadata: { |
|
platformUserHandle: string // player username |
|
clanTag: string |
|
placement: { |
|
value: number |
|
displayValue: string |
|
displayType: string |
|
} |
|
} |
|
stats: Types.Warzone.MatchDetails.Segment.Stats |
|
} |
|
export namespace Segment { |
|
export interface Stats { |
|
kills: Types.Segment.Stat |
|
deaths: Types.Segment.Stat |
|
damageTaken: Types.Segment.Stat |
|
damageDone: Types.Segment.Stat |
|
headshots: Types.Segment.Stat |
|
executions: Types.Segment.Stat |
|
assists: Types.Segment.Stat |
|
kdRatio: Types.Segment.Stat |
|
score: Types.Segment.Stat |
|
timePlayed: Types.Segment.Stat |
|
longestStreak: Types.Segment.Stat |
|
scorePerMinute: Types.Segment.Stat |
|
damageDonePerMinute: Types.Segment.Stat |
|
distanceTraveled: Types.Segment.Stat |
|
percentTimeMoving: Types.Segment.Stat |
|
} |
|
} |
|
} |
|
} |
|
} |