AI
This commit is contained in:
parent
1be9732279
commit
01aa843d0b
18 changed files with 4122 additions and 2393 deletions
|
|
@ -1,147 +1,429 @@
|
|||
# --- Script Setup ---
|
||||
Start-Transcript -Path '/var/log/dtch/get_matches.log' -Append
|
||||
Write-Output "Starting get_matches script at $(Get-Date)"
|
||||
Write-Output "Running from: $(Get-Location)"
|
||||
|
||||
if ($PSScriptRoot.length -eq 0) {
|
||||
$scriptroot = Get-Location
|
||||
}
|
||||
else {
|
||||
$scriptroot = $PSScriptRoot
|
||||
# Determine script root directory reliably
|
||||
if ($PSScriptRoot) {
|
||||
$scriptRoot = $PSScriptRoot
|
||||
} else {
|
||||
$scriptRoot = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition
|
||||
Write-Warning "PSScriptRoot not defined, using calculated path: $scriptRoot"
|
||||
}
|
||||
Write-Output "Script root identified as: $scriptRoot"
|
||||
|
||||
. $scriptroot\..\includes\ps1\lockfile.ps1
|
||||
new-lock -by "get_matches"
|
||||
# Define paths using Join-Path
|
||||
$includesPath = Join-Path -Path $scriptRoot -ChildPath "..\includes\ps1"
|
||||
$configPath = Join-Path -Path $scriptRoot -ChildPath "..\config"
|
||||
$dataPath = Join-Path -Path $scriptRoot -ChildPath "..\data"
|
||||
$matchesPath = Join-Path -Path $dataPath -ChildPath "matches"
|
||||
$matchesArchivePath = Join-Path -Path $matchesPath -ChildPath "archive"
|
||||
$playerDataJsonPath = Join-Path -Path $dataPath -ChildPath "player_data.json"
|
||||
$playerMatchesJsonPath = Join-Path -Path $dataPath -ChildPath "player_matches.json"
|
||||
$cachedMatchesJsonPath = Join-Path -Path $dataPath -ChildPath "cached_matches.json"
|
||||
|
||||
# Read the content of the file as a single string
|
||||
$fileContent = Get-Content -Path "$scriptroot/../config/config.php" -Raw
|
||||
$players = (Get-Content -Path "$scriptroot/../config/clanmembers.json" | ConvertFrom-Json).clanmembers
|
||||
# Use regex to match the apiKey value
|
||||
if ($fileContent -match "\`$apiKey\s*=\s*\'([^\']+)\'") {
|
||||
$apiKey = $matches[1]
|
||||
}
|
||||
else {
|
||||
Write-Output "API Key not found"
|
||||
}
|
||||
|
||||
$headers = @{
|
||||
'accept' = 'application/vnd.api+json'
|
||||
'Authorization' = "$apiKey"
|
||||
}
|
||||
$player_matches = @()
|
||||
try {
|
||||
$player_data = get-content "$scriptroot/../data/player_data.json" | convertfrom-json -Depth 100
|
||||
}
|
||||
catch {
|
||||
Write-Output 'Unable to read file exitin'
|
||||
exit
|
||||
}
|
||||
foreach ($player in $player_data) {
|
||||
$lastMatches = $player.relationships.matches.data.id #| Select-Object -First 10
|
||||
$playermatches = @()
|
||||
foreach ($match in $lastMatches) {
|
||||
Write-Host "Getting match for $($player.attributes.name) match: $match "
|
||||
if (Test-Path "$scriptroot/../data/matches/$match.json") {
|
||||
write-output "Getting $match from cache"
|
||||
$stats = get-content "$scriptroot/../data/matches/$match.json" | convertfrom-json
|
||||
# Ensure required directories exist
|
||||
@( $dataPath, $matchesPath, $matchesArchivePath ) | ForEach-Object {
|
||||
if (-not (Test-Path -Path $_ -PathType Container)) {
|
||||
Write-Warning "Directory not found at '$_'. Attempting to create."
|
||||
try {
|
||||
New-Item -Path $_ -ItemType Directory -Force -ErrorAction Stop | Out-Null
|
||||
Write-Output "Successfully created directory: $_"
|
||||
} catch {
|
||||
Write-Error "Failed to create directory '$_'. Please check permissions. Error: $($_.Exception.Message)"
|
||||
Stop-Transcript
|
||||
exit 1
|
||||
}
|
||||
else {
|
||||
$stats = Invoke-RestMethod -Uri "https://api.pubg.com/shards/steam/matches/$match" -Method GET -Headers $headers
|
||||
$sortedStats = $stats.included | Sort-Object { $_.attributes.stats.winplace }
|
||||
$stats.included = $sortedStats
|
||||
$stats | ConvertTo-Json -Depth 100 | Out-File "$scriptroot/../data/matches/$match.json"
|
||||
}
|
||||
}
|
||||
|
||||
# --- Locking ---
|
||||
$lockFilePath = Join-Path -Path $includesPath -ChildPath "lockfile.ps1"
|
||||
if (-not (Test-Path -Path $lockFilePath -PathType Leaf)) {
|
||||
Write-Error "Lockfile script not found at '$lockFilePath'. Cannot proceed."
|
||||
Stop-Transcript
|
||||
exit 1
|
||||
}
|
||||
. $lockFilePath
|
||||
New-Lock -by "get_matches" -ErrorAction Stop # Stop if locking fails
|
||||
|
||||
# --- Main Logic in Try/Finally for Lock Removal ---
|
||||
try {
|
||||
# --- Configuration Loading ---
|
||||
$apiKey = $null
|
||||
$clanMembers = @() # Renamed from $players for clarity
|
||||
|
||||
# Load API Key from config.php
|
||||
$phpConfigPath = Join-Path -Path $configPath -ChildPath "config.php"
|
||||
if (Test-Path -Path $phpConfigPath -PathType Leaf) {
|
||||
try {
|
||||
$fileContent = Get-Content -Path $phpConfigPath -Raw -ErrorAction Stop
|
||||
# Corrected regex for apiKey
|
||||
if ($fileContent -match '^\s*\$apiKey\s*=\s*''([^'']+)''') {
|
||||
$apiKey = $matches[1]
|
||||
Write-Output "API Key loaded successfully."
|
||||
} else {
|
||||
Write-Warning "API Key pattern not found in '$phpConfigPath'."
|
||||
}
|
||||
} catch {
|
||||
Write-Warning "Failed to read '$phpConfigPath'. Error: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
$playermatches += [PSCustomObject]@{
|
||||
stats = $stats.included.ATTRIBUTES.stats | where-object { $_.name -eq $player.attributes.name }
|
||||
matchType = $stats.data.attributes.matchtype
|
||||
gameMode = $stats.data.attributes.gameMode
|
||||
createdAt = $stats.data.attributes.createdAt
|
||||
mapName = $stats.data.attributes.mapName
|
||||
telemetry_url = ($stats.included.attributes | Where-Object { $_.name -eq 'telemetry' }).URL
|
||||
id = $stats.data.id
|
||||
} else {
|
||||
Write-Warning "Config file not found at '$phpConfigPath'."
|
||||
}
|
||||
|
||||
if (-not $apiKey) {
|
||||
Write-Error "API Key could not be loaded. Cannot proceed without API Key."
|
||||
throw "Missing API Key" # Throw to trigger finally block
|
||||
}
|
||||
|
||||
# Load Clan Members from clanmembers.json
|
||||
$clanMembersJsonPath = Join-Path -Path $configPath -ChildPath "clanmembers.json"
|
||||
if (Test-Path -Path $clanMembersJsonPath -PathType Leaf) {
|
||||
try {
|
||||
$clanMembersData = Get-Content -Path $clanMembersJsonPath -Raw | ConvertFrom-Json -ErrorAction Stop
|
||||
if ($clanMembersData -is [PSCustomObject] -and $clanMembersData.PSObject.Properties.Name -contains 'clanMembers' -and $clanMembersData.clanMembers -is [array]) {
|
||||
$clanMembers = $clanMembersData.clanMembers
|
||||
Write-Output "Clan members loaded successfully. Count: $($clanMembers.Count)"
|
||||
} else {
|
||||
Write-Warning "Invalid structure in '$clanMembersJsonPath'. Expected an object with a 'clanMembers' array."
|
||||
}
|
||||
} catch {
|
||||
Write-Warning "Failed to read or parse '$clanMembersJsonPath'. Error: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
} else {
|
||||
Write-Warning "Clan members file not found at '$clanMembersJsonPath'."
|
||||
}
|
||||
|
||||
$obj = [PSCustomObject]@{
|
||||
playername = $player.attributes.name
|
||||
player_matches = $playermatches
|
||||
|
||||
if ($clanMembers.Count -eq 0) {
|
||||
Write-Warning "No clan members loaded. Proceeding, but cached matches might be incomplete."
|
||||
# Decide if this is a fatal error or not
|
||||
}
|
||||
|
||||
$player_matches += $obj
|
||||
|
||||
}
|
||||
|
||||
if (test-path "$scriptroot/../data/player_matches.json") {
|
||||
try {
|
||||
$old_player_data = get-content "$scriptroot/../data/player_matches.json" | convertfrom-json -Depth 100
|
||||
}
|
||||
catch {
|
||||
Write-Output 'Unable to read file exitin'
|
||||
exit
|
||||
}
|
||||
$new_ids = ($player_matches.player_matches | where-object { $_.stats.winplace -eq 1 }).id
|
||||
$old_ids = ($old_player_data.player_matches | where-object { $_.stats.winplace -eq 1 }).id
|
||||
$new_win_matches = ((Compare-Object -ReferenceObject $old_ids -DifferenceObject $new_ids) | Where-Object { $_.SideIndicator -eq '=>' }).InputObject | Select-Object -Unique
|
||||
$new_win_matches = $old_player_data.new_win_matches + $new_win_matches | Select-Object -Unique
|
||||
$player_matches += [PSCustomObject]@{
|
||||
new_win_matches = $new_win_matches
|
||||
}
|
||||
|
||||
# Nieuwe verloren matches bepalen
|
||||
$new_loss_ids = ($player_matches.player_matches | Where-Object { $_.stats.winplace -ne 1 }).id
|
||||
$old_loss_ids = ($old_player_data.player_matches | Where-Object { $_.stats.winplace -ne 1 }).id
|
||||
$new_loss_matches = ((Compare-Object -ReferenceObject $old_loss_ids -DifferenceObject $new_loss_ids) | Where-Object { $_.SideIndicator -eq '=>' }).InputObject | Select-Object -Unique
|
||||
$new_loss_matches = $old_player_data.new_loss_matches + $new_loss_matches | Select-Object -Unique
|
||||
$player_matches += [PSCustomObject]@{
|
||||
new_loss_matches = $new_loss_matches
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$currentDateTime = Get-Date
|
||||
|
||||
# Get current timezone
|
||||
$currentTimezone = (Get-TimeZone).Id
|
||||
|
||||
# Format and parse the information into a string
|
||||
$formattedString = "$currentDateTime - Time Zone: $currentTimezone"
|
||||
# Output the formatted string
|
||||
$playermatches += [PSCustomObject]@{
|
||||
updated = $formattedString
|
||||
}
|
||||
|
||||
$player_matches | convertto-json -Depth 100 | out-file "$scriptroot/../data/player_matches.json"
|
||||
|
||||
write-output 'Cleaning matches'
|
||||
|
||||
$matchfiles = Get-ChildItem "$scriptroot/../data/matches" -Filter *.json
|
||||
$player_matches_object = @()
|
||||
foreach ($file in $matchfiles) {
|
||||
$filecontent = get-content $file | convertfrom-json
|
||||
$matchfiledate = $filecontent.data.attributes.createdAt
|
||||
if ($matchfiledate -lt (get-date).AddMonths(-3)) {
|
||||
write-output "archiving $matchfiledate"
|
||||
Move-Item -Path $file -Destination "$scriptroot/../data/matches/archive"
|
||||
}
|
||||
else {
|
||||
$result = ($filecontent.included | where-object { $_.type -eq 'participant' } | Where-Object { $players -contains $_.attributes.stats.name })
|
||||
$filecontent.data.id
|
||||
$result.count
|
||||
$player_matches_cached = ($filecontent.included | where-object { $_.type -eq 'participant' } | Where-Object { $players -contains $_.attributes.stats.name }).attributes.stats
|
||||
if ($null -ne $player_matches_cached) {
|
||||
$player_matches_object += [PSCustomObject]@{
|
||||
matchType = $filecontent.data.attributes.matchType
|
||||
gameMode = $filecontent.data.attributes.gameMode
|
||||
createdAt = $filecontent.data.attributes.createdAt
|
||||
mapName = $filecontent.data.attributes.mapName
|
||||
id = $filecontent.data.id
|
||||
stats = @($player_matches_cached)
|
||||
# --- Helper Function for API Calls (Copied from update_clan_members.ps1) ---
|
||||
function Invoke-PubgApi {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$Uri,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[hashtable]$Headers,
|
||||
[int]$RetryCount = 1,
|
||||
[int]$RetryDelaySeconds = 61
|
||||
)
|
||||
for ($attempt = 1; $attempt -le ($RetryCount + 1); $attempt++) {
|
||||
try {
|
||||
Write-Verbose "Attempting API call (Attempt $($attempt)): $Uri"
|
||||
$response = Invoke-RestMethod -Uri $Uri -Method GET -Headers $Headers -ErrorAction Stop
|
||||
if ($null -ne $response) { Write-Verbose "API call successful."; return $response }
|
||||
else { Write-Warning "API call to $Uri returned null or empty response."; return $null }
|
||||
} catch {
|
||||
$statusCode = $_.Exception.Response.StatusCode.value__
|
||||
$errorMessage = $_.Exception.Message
|
||||
Write-Warning "API call failed (Attempt $($attempt)). Status: $statusCode. Error: $errorMessage"
|
||||
if ($attempt -le $RetryCount -and $statusCode -eq 429) {
|
||||
Write-Warning "Rate limit hit. Sleeping for $RetryDelaySeconds seconds before retry..."
|
||||
Start-Sleep -Seconds $RetryDelaySeconds
|
||||
} elseif ($attempt -gt $RetryCount) { Write-Error "API call failed after $($attempt) attempts. URI: $Uri. Last Error: $errorMessage"; return $null }
|
||||
else { Write-Error "Non-retryable API error. URI: $Uri. Error: $errorMessage"; return $null }
|
||||
}
|
||||
}
|
||||
write-output "NEW $matchfiledate"
|
||||
return $null
|
||||
}
|
||||
}
|
||||
$player_matches_object | Sort-Object createdAt -Descending | convertto-json -Depth 100 | out-file "$scriptroot/../data/cached_matches.json"
|
||||
|
||||
remove-lock
|
||||
Stop-Transcript
|
||||
# --- Load Player Data (IDs and Match Lists) ---
|
||||
$playerData = $null
|
||||
if (Test-Path -Path $playerDataJsonPath -PathType Leaf) {
|
||||
try {
|
||||
# Assuming player_data.json contains an object with a 'data' array property
|
||||
$playerDataWrapper = Get-Content -Path $playerDataJsonPath | ConvertFrom-Json -Depth 100 -ErrorAction Stop
|
||||
if ($null -ne $playerDataWrapper -and $playerDataWrapper.PSObject.Properties.Name -contains 'data' -and $playerDataWrapper.data -is [array]) {
|
||||
$playerData = $playerDataWrapper.data
|
||||
Write-Output "Successfully loaded player data. Count: $($playerData.Count)"
|
||||
} else {
|
||||
Write-Error "Invalid structure in '$playerDataJsonPath'. Expected object with 'data' array. Cannot proceed."
|
||||
throw "Invalid player data structure."
|
||||
}
|
||||
} catch {
|
||||
Write-Error "Error reading '$playerDataJsonPath': $($_.Exception.Message). Cannot proceed."
|
||||
throw $_
|
||||
}
|
||||
} else {
|
||||
Write-Error "Player data file not found at '$playerDataJsonPath'. Run update_clan_members.ps1 first. Cannot proceed."
|
||||
throw "Missing player data file."
|
||||
}
|
||||
|
||||
# --- Fetch and Process Matches for Each Player ---
|
||||
Write-Output "Fetching and processing matches for $($playerData.Count) players..."
|
||||
$allPlayerMatchDetails = @() # Store processed match details for all players
|
||||
$apiHeaders = @{
|
||||
'accept' = 'application/vnd.api+json'
|
||||
'Authorization' = "Bearer $apiKey"
|
||||
}
|
||||
$matchesFetched = 0
|
||||
$matchesCached = 0
|
||||
|
||||
foreach ($player in $playerData) {
|
||||
# Validate player structure
|
||||
if ($null -eq $player.attributes -or $null -eq $player.attributes.name -or $null -eq $player.relationships.matches.data) {
|
||||
Write-Warning "Skipping player due to missing attributes, name, or match data: $($player | Out-String)"
|
||||
continue
|
||||
}
|
||||
$playerName = $player.attributes.name
|
||||
$playerMatchIds = $player.relationships.matches.data.id
|
||||
Write-Output "Processing player: $playerName ($($playerMatchIds.Count) recent matches)"
|
||||
|
||||
$currentPlayerMatches = @()
|
||||
foreach ($matchId in $playerMatchIds) {
|
||||
if (-not $matchId) { Write-Warning "Skipping null/empty match ID for player $playerName."; continue }
|
||||
|
||||
Write-Verbose "Getting match details for $playerName, Match ID: $matchId"
|
||||
$matchJsonPath = Join-Path -Path $matchesPath -ChildPath "$matchId.json"
|
||||
$matchStats = $null
|
||||
|
||||
# Check cache first
|
||||
if (Test-Path -Path $matchJsonPath -PathType Leaf) {
|
||||
Write-Verbose "Getting $matchId from cache."
|
||||
try {
|
||||
$matchStats = Get-Content -Path $matchJsonPath | ConvertFrom-Json -Depth 100 -ErrorAction Stop
|
||||
if ($null -eq $matchStats) { Write-Warning "Failed to parse cached match file: $matchJsonPath" }
|
||||
else { $matchesCached++ }
|
||||
} catch {
|
||||
Write-Warning "Error reading cached match file '$matchJsonPath': $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
# Fetch from API if not cached or cache failed
|
||||
if ($null -eq $matchStats) {
|
||||
$apiUrl = "https://api.pubg.com/shards/steam/matches/$matchId"
|
||||
Write-Output "Fetching match $matchId from API..."
|
||||
$matchStats = Invoke-PubgApi -Uri $apiUrl -Headers $apiHeaders
|
||||
|
||||
if ($null -ne $matchStats) {
|
||||
$matchesFetched++
|
||||
# Sort included participants by winPlace before saving (optional, but done in original)
|
||||
try {
|
||||
if ($null -ne $matchStats.included -and $matchStats.included -is [array]) {
|
||||
$matchStats.included = $matchStats.included | Sort-Object -Property { $_.attributes.stats.winPlace } -ErrorAction SilentlyContinue
|
||||
}
|
||||
} catch { Write-Warning "Could not sort 'included' array for match $matchId." }
|
||||
|
||||
# Save to cache
|
||||
try {
|
||||
$matchStats | ConvertTo-Json -Depth 100 | Out-File -FilePath $matchJsonPath -Encoding UTF8 -ErrorAction Stop
|
||||
Write-Verbose "Saved match $matchId to cache."
|
||||
} catch {
|
||||
Write-Warning "Failed to save match $matchId to cache '$matchJsonPath'. Error: $($_.Exception.Message)"
|
||||
}
|
||||
} else {
|
||||
Write-Warning "Failed to fetch match $matchId from API for player $playerName."
|
||||
continue # Skip this match if API fetch failed
|
||||
}
|
||||
}
|
||||
|
||||
# Process the retrieved/cached match stats
|
||||
if ($null -ne $matchStats -and $null -ne $matchStats.data -and $null -ne $matchStats.included) {
|
||||
# Find the specific player's stats within the 'included' array
|
||||
$playerSpecificStats = $null
|
||||
if ($matchStats.included -is [array]) {
|
||||
$playerSpecificStats = $matchStats.included |
|
||||
Where-Object { $_.type -eq 'participant' -and ($_.attributes.stats.name -eq $playerName) } |
|
||||
Select-Object -First 1 -ExpandProperty attributes | Select-Object -ExpandProperty stats
|
||||
}
|
||||
|
||||
# Find telemetry URL
|
||||
$telemetryUrl = $null
|
||||
if ($matchStats.included -is [array]) {
|
||||
$telemetryAsset = $matchStats.included | Where-Object { $_.type -eq 'asset' -and $_.attributes.name -eq 'telemetry' } | Select-Object -First 1
|
||||
if ($telemetryAsset) { $telemetryUrl = $telemetryAsset.attributes.URL }
|
||||
}
|
||||
|
||||
if ($null -ne $playerSpecificStats) {
|
||||
$currentPlayerMatches += [PSCustomObject]@{
|
||||
stats = $playerSpecificStats # Just the stats object for this player
|
||||
matchType = $matchStats.data.attributes.matchType
|
||||
gameMode = $matchStats.data.attributes.gameMode
|
||||
createdAt = $matchStats.data.attributes.createdAt
|
||||
mapName = $matchStats.data.attributes.mapName
|
||||
telemetry_url = $telemetryUrl
|
||||
id = $matchStats.data.id
|
||||
}
|
||||
} else {
|
||||
Write-Warning "Could not find stats for player $playerName within match $matchId data."
|
||||
}
|
||||
} else {
|
||||
Write-Warning "Match data structure for $matchId is invalid or incomplete after retrieval/caching."
|
||||
}
|
||||
} # End foreach matchId
|
||||
|
||||
# Add the processed matches for the current player to the main list
|
||||
$allPlayerMatchDetails += [PSCustomObject]@{
|
||||
playername = $playerName
|
||||
player_matches = $currentPlayerMatches # Array of processed match objects for this player
|
||||
}
|
||||
Write-Output "Finished processing matches for $playerName. Found $($currentPlayerMatches.Count) valid entries."
|
||||
} # End foreach player
|
||||
Write-Output "Finished fetching/processing all matches. API Fetches: $matchesFetched, Cache Hits: $matchesCached."
|
||||
|
||||
# --- Compare with Old Data & Identify New Wins/Losses ---
|
||||
Write-Output "Comparing current matches with old data..."
|
||||
$oldPlayerMatchData = $null
|
||||
$newWinMatchesList = @()
|
||||
$newLossMatchesList = @()
|
||||
|
||||
if (Test-Path -Path $playerMatchesJsonPath -PathType Leaf) {
|
||||
try {
|
||||
$oldPlayerMatchData = Get-Content -Path $playerMatchesJsonPath | ConvertFrom-Json -Depth 100 -ErrorAction Stop
|
||||
if ($null -eq $oldPlayerMatchData) {
|
||||
Write-Warning "Failed to parse old player matches file: $playerMatchesJsonPath. Cannot compare for new wins/losses."
|
||||
} else {
|
||||
Write-Output "Successfully loaded old player matches data for comparison."
|
||||
|
||||
# Extract all current match IDs and old match IDs safely
|
||||
$currentMatchIds = ($allPlayerMatchDetails.player_matches.id | Select-Object -Unique)
|
||||
$oldMatchIds = $null
|
||||
if ($oldPlayerMatchData -is [array]) {
|
||||
$oldMatchIds = ($oldPlayerMatchData.player_matches.id | Select-Object -Unique)
|
||||
} else {
|
||||
Write-Warning "Old player matches data is not in the expected array format."
|
||||
}
|
||||
|
||||
if ($null -ne $oldMatchIds) {
|
||||
# Compare IDs to find newly added matches
|
||||
$newMatchIds = (Compare-Object -ReferenceObject $oldMatchIds -DifferenceObject $currentMatchIds | Where-Object { $_.SideIndicator -eq '=>' }).InputObject | Select-Object -Unique
|
||||
Write-Output "Found $($newMatchIds.Count) new match IDs."
|
||||
|
||||
# Identify new wins and losses among the new matches
|
||||
$newMatchesDetails = $allPlayerMatchDetails.player_matches | Where-Object { $newMatchIds -contains $_.id }
|
||||
$newWinMatchesList = ($newMatchesDetails | Where-Object { $_.stats.winPlace -eq 1 }).id | Select-Object -Unique
|
||||
$newLossMatchesList = ($newMatchesDetails | Where-Object { $_.stats.winPlace -ne 1 }).id | Select-Object -Unique
|
||||
|
||||
Write-Output "Identified $($newWinMatchesList.Count) new wins and $($newLossMatchesList.Count) new losses."
|
||||
|
||||
# Combine with potentially existing lists from old data (if format was correct)
|
||||
$oldWinMatches = $oldPlayerMatchData | Where-Object { $_.PSObject.Properties.Name -eq 'new_win_matches' } | Select-Object -ExpandProperty new_win_matches
|
||||
$oldLossMatches = $oldPlayerMatchData | Where-Object { $_.PSObject.Properties.Name -eq 'new_loss_matches' } | Select-Object -ExpandProperty new_loss_matches
|
||||
|
||||
if ($oldWinMatches -is [array]) { $newWinMatchesList = ($oldWinMatches + $newWinMatchesList) | Select-Object -Unique }
|
||||
if ($oldLossMatches -is [array]) { $newLossMatchesList = ($oldLossMatches + $newLossMatchesList) | Select-Object -Unique }
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Write-Warning "Error reading or processing old player matches file '$playerMatchesJsonPath': $($_.Exception.Message)"
|
||||
}
|
||||
} else {
|
||||
Write-Output "Old player matches file not found. Cannot compare for new wins/losses."
|
||||
# If no old file, all current wins/losses are "new" for the first run
|
||||
$newWinMatchesList = ($allPlayerMatchDetails.player_matches | Where-Object { $_.stats.winPlace -eq 1 }).id | Select-Object -Unique
|
||||
$newLossMatchesList = ($allPlayerMatchDetails.player_matches | Where-Object { $_.stats.winPlace -ne 1 }).id | Select-Object -Unique
|
||||
Write-Output "Treating all current wins ($($newWinMatchesList.Count)) and losses ($($newLossMatchesList.Count)) as new."
|
||||
}
|
||||
|
||||
# Add the lists of new wins/losses to the data structure
|
||||
$allPlayerMatchDetails += [PSCustomObject]@{ new_win_matches = $newWinMatchesList }
|
||||
$allPlayerMatchDetails += [PSCustomObject]@{ new_loss_matches = $newLossMatchesList }
|
||||
|
||||
# Add update timestamp
|
||||
$currentDateTime = Get-Date
|
||||
$currentTimezone = (Get-TimeZone).Id
|
||||
$formattedString = "$currentDateTime - Time Zone: $currentTimezone"
|
||||
$allPlayerMatchDetails += [PSCustomObject]@{ updated = $formattedString }
|
||||
Write-Output "Added update timestamp and new win/loss lists."
|
||||
|
||||
# --- Save Updated Player Matches Data ---
|
||||
try {
|
||||
$allPlayerMatchDetails | ConvertTo-Json -Depth 100 | Out-File -FilePath $playerMatchesJsonPath -Encoding UTF8 -ErrorAction Stop
|
||||
Write-Output "Updated player matches data saved to '$playerMatchesJsonPath'"
|
||||
} catch {
|
||||
Write-Error "Failed to save updated player matches data to '$playerMatchesJsonPath'. Error: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
# --- Clean Old Match Files & Create Cached Summary ---
|
||||
Write-Output "Cleaning old match files and creating cached summary..."
|
||||
$cachedMatches = @()
|
||||
$archivedMatchFiles = 0
|
||||
$processedMatchFiles = 0
|
||||
$monthsToKeepMatches = -3 # How long to keep individual match files
|
||||
|
||||
try {
|
||||
$matchFiles = Get-ChildItem -Path $matchesPath -Filter *.json -File -ErrorAction SilentlyContinue
|
||||
if ($matchFiles) {
|
||||
$archiveThreshold = (Get-Date).AddMonths($monthsToKeepMatches)
|
||||
Write-Output "Archiving match files older than: $archiveThreshold"
|
||||
|
||||
foreach ($file in $matchFiles) {
|
||||
$processedMatchFiles++
|
||||
try {
|
||||
$fileContent = Get-Content -Path $file.FullName | ConvertFrom-Json -Depth 100 -ErrorAction Stop
|
||||
# Validate essential structure
|
||||
if ($null -eq $fileContent -or $null -eq $fileContent.data.attributes.createdAt -or $null -eq $fileContent.included) {
|
||||
Write-Warning "Skipping invalid match file: $($file.Name)"
|
||||
continue
|
||||
}
|
||||
|
||||
$matchFileDate = $null
|
||||
try { $matchFileDate = [datetime]$fileContent.data.attributes.createdAt } catch { Write-Warning "Could not parse date in match file $($file.Name)" }
|
||||
|
||||
# Archive old files
|
||||
if ($null -ne $matchFileDate -and $matchFileDate -lt $archiveThreshold) {
|
||||
Write-Verbose "Archiving match file: $($file.Name)"
|
||||
Move-Item -Path $file.FullName -Destination $matchesArchivePath -Force -ErrorAction SilentlyContinue
|
||||
if ($?) { $archivedMatchFiles++ }
|
||||
else { Write-Warning "Failed to archive match file: $($file.Name)" }
|
||||
} else {
|
||||
# Process file for cached summary if it's recent enough
|
||||
$matchAttributes = $fileContent.data.attributes
|
||||
$matchId = $fileContent.data.id
|
||||
|
||||
# Find stats for clan members within this match
|
||||
$clanMemberStatsInMatch = @()
|
||||
if ($fileContent.included -is [array]) {
|
||||
$clanMemberStatsInMatch = $fileContent.included |
|
||||
Where-Object { $_.type -eq 'participant' -and $clanMembers -contains $_.attributes.stats.name } |
|
||||
Select-Object -ExpandProperty attributes | Select-Object -ExpandProperty stats
|
||||
}
|
||||
|
||||
if ($clanMemberStatsInMatch.Count -gt 0) {
|
||||
$cachedMatches += [PSCustomObject]@{
|
||||
matchType = $matchAttributes.matchType
|
||||
gameMode = $matchAttributes.gameMode
|
||||
createdAt = $matchAttributes.createdAt
|
||||
mapName = $matchAttributes.mapName
|
||||
id = $matchId
|
||||
stats = @($clanMemberStatsInMatch) # Ensure stats is always an array
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Write-Warning "Error processing match file '$($file.Name)': $($_.Exception.Message)"
|
||||
}
|
||||
} # End foreach file
|
||||
Write-Output "Processed $processedMatchFiles match files. Archived: $archivedMatchFiles."
|
||||
|
||||
# Save the cached summary object
|
||||
if ($cachedMatches.Count -gt 0) {
|
||||
try {
|
||||
$cachedMatches | Sort-Object createdAt -Descending | ConvertTo-Json -Depth 100 | Out-File -FilePath $cachedMatchesJsonPath -Encoding UTF8 -ErrorAction Stop
|
||||
Write-Output "Cached matches summary saved to '$cachedMatchesJsonPath'. Count: $($cachedMatches.Count)"
|
||||
} catch {
|
||||
Write-Error "Failed to save cached matches summary to '$cachedMatchesJsonPath'. Error: $($_.Exception.Message)"
|
||||
}
|
||||
} else {
|
||||
Write-Output "No recent matches found containing clan members for cached summary."
|
||||
# Optionally clear or save an empty array to the cache file
|
||||
# @() | ConvertTo-Json | Out-File -FilePath $cachedMatchesJsonPath -Encoding UTF8
|
||||
}
|
||||
|
||||
} else {
|
||||
Write-Output "No match files found in '$matchesPath' to process or clean."
|
||||
}
|
||||
} catch {
|
||||
Write-Warning "Error during match file cleaning/caching process: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
} # End Main Try Block
|
||||
finally {
|
||||
# --- Cleanup ---
|
||||
Write-Output "Script finished at $(Get-Date)."
|
||||
Remove-Lock # Ensure lock is always removed
|
||||
Stop-Transcript
|
||||
}
|
||||
|
|
@ -1,307 +1,575 @@
|
|||
# --- Script Setup ---
|
||||
Start-Transcript -Path '/var/log/dtch/matchparser.log' -Append
|
||||
Write-Output 'Running from'
|
||||
(Get-Location).path
|
||||
Write-Output "Starting matchparser script at $(Get-Date)"
|
||||
Write-Output "Running from: $(Get-Location)"
|
||||
|
||||
if ($PSScriptRoot.length -eq 0) {
|
||||
$scriptroot = Get-Location
|
||||
}
|
||||
else {
|
||||
$scriptroot = $PSScriptRoot
|
||||
# Determine script root directory reliably
|
||||
if ($PSScriptRoot) {
|
||||
$scriptRoot = $PSScriptRoot
|
||||
} else {
|
||||
$scriptRoot = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition
|
||||
Write-Warning "PSScriptRoot not defined, using calculated path: $scriptRoot"
|
||||
}
|
||||
Write-Output "Script root identified as: $scriptRoot"
|
||||
|
||||
. $scriptroot\..\includes\ps1\lockfile.ps1
|
||||
new-lock -by "matchparser"
|
||||
##SETTINGS
|
||||
$monthsback = -3 # how many months back to look for matches
|
||||
##END OF SETTINGS
|
||||
function Get-Change {
|
||||
param (
|
||||
[double]$OldWinRatio,
|
||||
[double]$NewWinRatio
|
||||
)
|
||||
|
||||
$change = ($OldWinRatio -eq 0) ? (($NewWinRatio -eq 0) ? 0 : $NewWinRatio) : ($NewWinRatio - $OldWinRatio)
|
||||
|
||||
return [math]::Round($change, 2)
|
||||
}
|
||||
|
||||
function Get-winratio {
|
||||
param (
|
||||
[int]$player_wins,
|
||||
[int]$player_matches
|
||||
)
|
||||
if ($player_wins -eq 0 -or $player_matches -eq 0) {
|
||||
$winratio = 0
|
||||
}
|
||||
else {
|
||||
$winratio = ($player_wins / $player_matches) * 100
|
||||
}
|
||||
return $winratio
|
||||
}
|
||||
function get-killstats {
|
||||
param (
|
||||
$player_name,
|
||||
$telemetry,
|
||||
$matchType,
|
||||
$gameMode
|
||||
)
|
||||
$LOGPLAYERKILLV2 = $telemetry | where-object { $_._T -eq 'LOGPLAYERKILLV2' }
|
||||
$kills = $LOGPLAYERKILLV2 | where-object { $_.killer.name -eq $player_name }
|
||||
$deaths = $LOGPLAYERKILLV2 | where-object { $_.victim.name -eq $player_name -and $_.finisher.name.count -ge 1 }
|
||||
$HumanDmg = $([math]::Round(($telemetry | Where-Object { $_._T -eq 'LOGPLAYERTAKEDAMAGE' -and $_.attacker.name -eq $player_name -and $_.victim.accountId -notlike "ai.*" -and $_.victim.teamId -ne $_.attacker.teamId } | Measure-Object -Property damage -Sum).Sum))
|
||||
return @{
|
||||
playername = $player_name
|
||||
humankills = ($kills | where-object { $_.victim.accountId -notlike 'ai.*' }).count
|
||||
kills = $kills.count
|
||||
deaths = ($deaths).count
|
||||
gameMode = $gameMode
|
||||
matchType = $matchType
|
||||
dbno = ($kills | where-object { $_.dBNOMaker.name -eq $player_name }).count
|
||||
HumanDmg = $HumanDmg
|
||||
|
||||
# Define paths using Join-Path
|
||||
$includesPath = Join-Path -Path $scriptRoot -ChildPath "..\includes\ps1"
|
||||
$dataPath = Join-Path -Path $scriptRoot -ChildPath "..\data"
|
||||
$killStatsPath = Join-Path -Path $dataPath -ChildPath "killstats"
|
||||
$archivePath = Join-Path -Path $killStatsPath -ChildPath "archive" # Archive within killstats
|
||||
$telemetryCachePath = Join-Path -Path $dataPath -ChildPath "telemetry_cache"
|
||||
$playerMatchesJsonPath = Join-Path -Path $dataPath -ChildPath "player_matches.json"
|
||||
$lastStatsJsonPath = Join-Path -Path $dataPath -ChildPath "player_last_stats.json"
|
||||
$archiveDir = Join-Path -Path $dataPath -ChildPath "archive" # Separate archive for player_last_stats
|
||||
|
||||
# Ensure required directories exist
|
||||
@( $dataPath, $killStatsPath, $archivePath, $telemetryCachePath, $archiveDir ) | ForEach-Object {
|
||||
if (-not (Test-Path -Path $_ -PathType Container)) {
|
||||
Write-Warning "Directory not found at '$_'. Attempting to create."
|
||||
try {
|
||||
New-Item -Path $_ -ItemType Directory -Force -ErrorAction Stop | Out-Null
|
||||
Write-Output "Successfully created directory: $_"
|
||||
} catch {
|
||||
Write-Error "Failed to create directory '$_'. Please check permissions. Error: $($_.Exception.Message)"
|
||||
Stop-Transcript
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- Locking ---
|
||||
$lockFilePath = Join-Path -Path $includesPath -ChildPath "lockfile.ps1"
|
||||
if (-not (Test-Path -Path $lockFilePath -PathType Leaf)) {
|
||||
Write-Error "Lockfile script not found at '$lockFilePath'. Cannot proceed."
|
||||
Stop-Transcript
|
||||
exit 1
|
||||
}
|
||||
. $lockFilePath
|
||||
New-Lock -by "matchparser" -ErrorAction Stop # Stop if locking fails
|
||||
|
||||
# --- Main Logic in Try/Finally for Lock Removal ---
|
||||
try {
|
||||
$filesarray = @()
|
||||
$files = Get-ChildItem -Path "$scriptroot/../data/archive/" -File -ErrorAction Stop
|
||||
foreach ($file in $files) {
|
||||
$dateinfile = $file.Name.split('_')[0]
|
||||
$format = 'yyyy-MM-ddTHH-mm-ss\Z'
|
||||
$culture = [Globalization.CultureInfo]::InvariantCulture
|
||||
$dateTime = [datetime]::ParseExact($dateinfile, $format, $culture)
|
||||
$filesarray += [PSCustomObject]@{name = $file.Name; date = $dateTime }
|
||||
# --- Settings ---
|
||||
$monthsBack = -3 # How many months back to look for matches for aggregation
|
||||
Write-Output "Using monthsBack setting: $monthsBack"
|
||||
|
||||
# --- Helper Functions ---
|
||||
function Get-Change {
|
||||
param (
|
||||
[double]$OldWinRatio,
|
||||
[double]$NewWinRatio
|
||||
)
|
||||
# Ensure inputs are treated as numbers, default to 0 if null/invalid
|
||||
$old = if ($OldWinRatio -is [double]) { $OldWinRatio } else { 0.0 }
|
||||
$new = if ($NewWinRatio -is [double]) { $NewWinRatio } else { 0.0 }
|
||||
|
||||
# Calculate change: If old is 0, change is simply the new ratio. Otherwise, it's the difference.
|
||||
$change = ($old -eq 0) ? $new : ($new - $old)
|
||||
return [math]::Round($change, 2)
|
||||
}
|
||||
|
||||
try { $latestFile = ($filesarray | where-object { ($_.date -gt (get-date).AddDays(-2)) -and ($_.date -lt (get-date).AddDays(-1)) } | Sort-Object date)[0] }
|
||||
catch { $latestFile = ($filesarray | sort-object date )[-1] }
|
||||
$latestFile = Get-Item -Path "$scriptroot/../data/archive/$($latestFile.name)"
|
||||
Write-Output "Found file $($latestFile.FullName)"
|
||||
|
||||
}
|
||||
catch {
|
||||
$latestFile = @()
|
||||
}
|
||||
|
||||
# Display the result
|
||||
if ($latestFile.FullName) {
|
||||
write-host "getting info from $($latestFile.FullName)"
|
||||
$oldstats = get-content $latestFile.FullName | ConvertFrom-Json
|
||||
}
|
||||
else {
|
||||
write-output 'setting old stats var empty'
|
||||
$oldstats = @()
|
||||
}
|
||||
|
||||
try {
|
||||
$all_player_matches = get-content "$scriptroot/../data/player_matches.json" | convertfrom-json -Depth 100
|
||||
}
|
||||
catch {
|
||||
Write-Output 'Unable to read file exitin'
|
||||
exit
|
||||
}
|
||||
|
||||
|
||||
|
||||
foreach ($player in $all_player_matches) {
|
||||
if ($player.psobject.properties.name -eq 'new_win_matches') {
|
||||
continue
|
||||
function Get-WinRatio {
|
||||
param (
|
||||
[int]$playerWins,
|
||||
[int]$playerMatches
|
||||
)
|
||||
if ($playerMatches -gt 0) {
|
||||
# Calculate win ratio percentage
|
||||
return [math]::Round(($playerWins / $playerMatches) * 100, 2) # Round percentage
|
||||
} else {
|
||||
return 0.0 # Return 0.0 for consistency if no matches played
|
||||
}
|
||||
}
|
||||
$player_name = $player.playername
|
||||
|
||||
foreach ($match in $player.player_matches) {
|
||||
write-output "Analyzing match $($match.id) for player $player_name"
|
||||
if (!(Test-Path -path "$scriptroot/../data/killstats/$($match.id)_$player_name.json" )) {
|
||||
$telemetryfile = "$scriptroot/../data/telemetry_cache/$($match.telemetry_url.split("/")[-1])"
|
||||
if (!(test-path -Path $telemetryfile)) {
|
||||
write-output "Saving $telemetryfile"
|
||||
$telemetry_content = (Invoke-WebRequest -Uri $match.telemetry_url).content
|
||||
$telemetry_content | out-file $telemetryfile
|
||||
$telemetry = $telemetry_content | ConvertFrom-Json
|
||||
}
|
||||
else {
|
||||
write-output "Getting from cache $telemetryfile"
|
||||
$telemetry = get-content $telemetryfile | convertfrom-json
|
||||
}
|
||||
function Get-KillStatsFromTelemetry {
|
||||
param (
|
||||
[string]$playerName,
|
||||
[array]$telemetryEvents, # Expecting pre-filtered events
|
||||
[string]$matchType,
|
||||
[string]$gameMode
|
||||
)
|
||||
|
||||
# Validate input
|
||||
if (-not $playerName -or $null -eq $telemetryEvents) {
|
||||
Write-Warning "Get-KillStatsFromTelemetry: Invalid input (PlayerName or TelemetryEvents)."
|
||||
return $null
|
||||
}
|
||||
|
||||
# Filter relevant events once
|
||||
$killEvents = $telemetryEvents | Where-Object { $_._T -eq 'LogPlayerKillV2' }
|
||||
$damageEvents = $telemetryEvents | Where-Object { $_._T -eq 'LogPlayerTakeDamage' }
|
||||
|
||||
# Calculate Kills
|
||||
$playerKills = $killEvents | Where-Object { $null -ne $_.killer -and $_.killer.name -eq $playerName }
|
||||
$totalKillsCount = $playerKills.Count
|
||||
$humanKillsCount = ($playerKills | Where-Object { $null -ne $_.victim -and $_.victim.accountId -notlike 'ai.*' }).Count
|
||||
$dbnoCount = ($playerKills | Where-Object { $null -ne $_.dBNOId }).Count # Check if DBNO event exists
|
||||
|
||||
# Calculate Deaths (where player is victim and finished by someone)
|
||||
# Note: Finisher check might be complex depending on data structure, adjust if needed
|
||||
$playerDeaths = $killEvents | Where-Object { $null -ne $_.victim -and $_.victim.name -eq $playerName -and $null -ne $_.finisher }
|
||||
$deathsCount = $playerDeaths.Count
|
||||
|
||||
# Calculate Human Damage Dealt
|
||||
$humanDamageDealt = 0
|
||||
try {
|
||||
$humanDamageEvents = $damageEvents | Where-Object {
|
||||
$null -ne $_.attacker -and $_.attacker.name -eq $playerName -and
|
||||
$null -ne $_.victim -and $_.victim.accountId -notlike "ai.*" -and
|
||||
$_.victim.teamId -ne $_.attacker.teamId
|
||||
}
|
||||
if ($humanDamageEvents) {
|
||||
$humanDamageDealt = ($humanDamageEvents | Measure-Object -Property damage -Sum).Sum
|
||||
}
|
||||
} catch {
|
||||
$errorMessage = $_.Exception.Message
|
||||
Write-Warning ("Error calculating HumanDmg for {0}: {1}" -f $playerName, $errorMessage)
|
||||
}
|
||||
|
||||
write-output "Analyzing for player $player_name telemetry: $($match.telemetry_url)"
|
||||
$killstat = get-killstats -player_name $player_name -telemetry ($telemetry | where-object { ($_._T -eq 'LOGPLAYERTAKEDAMAGE') -or ($_._T -eq 'LOGPLAYERKILLV2') }) -gameMode $match.gameMode -matchType $match.matchType
|
||||
|
||||
|
||||
$savekillstats = @{
|
||||
matchid = $match.id
|
||||
created = $match.createdAt
|
||||
stats = $killstat
|
||||
deathType = $match.stats.deathType
|
||||
winplace = (($all_player_matches | where-object { $_.playername -eq $player_name } ).player_matches | where-object { $_.id -eq $match.id }).stats.winplace
|
||||
return @{
|
||||
playername = $playerName
|
||||
humankills = $humanKillsCount
|
||||
kills = $totalKillsCount
|
||||
deaths = $deathsCount # Note: This counts finishes, might differ from match end death reason
|
||||
gameMode = $gameMode
|
||||
matchType = $matchType
|
||||
dbno = $dbnoCount
|
||||
HumanDmg = [math]::Round($humanDamageDealt) # Round final value
|
||||
}
|
||||
}
|
||||
|
||||
# --- Load Old Stats Archive ---
|
||||
$oldStats = $null
|
||||
$latestArchivePath = $null
|
||||
try {
|
||||
$archiveFiles = Get-ChildItem -Path $archiveDir -Filter "*_player_last_stats.json" -File -ErrorAction SilentlyContinue
|
||||
if ($archiveFiles) {
|
||||
# Find the most recent archive file based on filename date
|
||||
$latestArchiveFile = $archiveFiles | Sort-Object -Property Name -Descending | Select-Object -First 1
|
||||
$latestArchivePath = $latestArchiveFile.FullName
|
||||
Write-Output "Attempting to load old stats from: $latestArchivePath"
|
||||
$oldStats = Get-Content -Path $latestArchivePath | ConvertFrom-Json -ErrorAction Stop
|
||||
if ($null -eq $oldStats) {
|
||||
Write-Warning "Failed to parse old stats file: $latestArchivePath"
|
||||
} else {
|
||||
Write-Output "Successfully loaded old stats."
|
||||
}
|
||||
Write-Output "Writing to file $scriptroot/../data/killstats/$($match.id)_$player_name.json"
|
||||
$savekillstats | ConvertTo-Json | out-file "$scriptroot/../data/killstats/$($match.id)_$player_name.json"
|
||||
|
||||
|
||||
}
|
||||
else {
|
||||
Write-Output "$($match.id) already in cache"
|
||||
} else {
|
||||
Write-Output "No old stats archive files found in '$archiveDir'."
|
||||
}
|
||||
} catch {
|
||||
Write-Warning "Error accessing or processing archive directory '$archiveDir': $($_.Exception.Message)"
|
||||
$oldStats = $null # Ensure it's null on error
|
||||
}
|
||||
}
|
||||
|
||||
$killstats = @()
|
||||
$matchfiles = Get-ChildItem "$scriptroot/../data/killstats/" -File -Filter *.json
|
||||
|
||||
$killstats_clan_matches_gt_1 = @()
|
||||
$killstats_clan_matches_gt_2 = @()
|
||||
$killstats_clan_matches_gt_3 = @()
|
||||
$guids = $matchfiles.Name | ForEach-Object { $_.Split("_")[0] }
|
||||
$groupedGuids_clan_matches_gt_1 = $guids | Group-Object | Where-Object { $_.Count -gt 1 }
|
||||
$groupedGuids_clan_matches_gt_2 = $guids | Group-Object | Where-Object { $_.Count -gt 2 }
|
||||
$groupedGuids_clan_matches_gt_3 = $guids | Group-Object | Where-Object { $_.Count -gt 3 }
|
||||
|
||||
$last_month = (get-date).AddMonths($monthsback)
|
||||
foreach ($file in $matchfiles) {
|
||||
$json = get-content $file | ConvertFrom-Json
|
||||
if ($json.created -gt $last_month) {
|
||||
$killstats += $json
|
||||
if ($groupedGuids_clan_matches_gt_1.Name -contains $json.matchid) {
|
||||
$killstats_clan_matches_gt_1 += $json
|
||||
}
|
||||
if ($groupedGuids_clan_matches_gt_2.Name -contains $json.matchid) {
|
||||
$killstats_clan_matches_gt_2 += $json
|
||||
}
|
||||
if ($groupedGuids_clan_matches_gt_3.Name -contains $json.matchid) {
|
||||
$killstats_clan_matches_gt_3 += $json
|
||||
}
|
||||
# Use an empty hashtable if old stats couldn't be loaded, to prevent errors later
|
||||
if ($null -eq $oldStats) {
|
||||
Write-Output "Initializing old stats as empty."
|
||||
$oldStats = @{}
|
||||
}
|
||||
else {
|
||||
write-output "Archiving $($file.name)"
|
||||
Move-Item -Path $file.FullName -Destination "$scriptroot/../data/killstats/archive/." -Force -Verbose
|
||||
}
|
||||
}
|
||||
|
||||
function Get-MatchStatsPlayer {
|
||||
param (
|
||||
[switch] $GameMode,
|
||||
[switch] $MatchType,
|
||||
[string] $typemodevalue,
|
||||
[array] $playernames,
|
||||
[string] $friendlyname,
|
||||
[array] $killstats,
|
||||
[string] $sortstat
|
||||
|
||||
# --- Load Current Player Matches ---
|
||||
$allPlayerMatches = $null
|
||||
if (Test-Path -Path $playerMatchesJsonPath -PathType Leaf) {
|
||||
try {
|
||||
$allPlayerMatches = Get-Content -Path $playerMatchesJsonPath | ConvertFrom-Json -Depth 100 -ErrorAction Stop
|
||||
if ($null -eq $allPlayerMatches -or -not ($allPlayerMatches -is [array])) {
|
||||
Write-Error "Failed to parse or invalid structure in '$playerMatchesJsonPath'. Cannot proceed."
|
||||
throw "Invalid player matches data." # Throw to trigger finally block
|
||||
}
|
||||
Write-Output "Successfully loaded player matches data. Entry count: $($allPlayerMatches.Count)"
|
||||
} catch {
|
||||
Write-Error "Error reading '$playerMatchesJsonPath': $($_.Exception.Message). Cannot proceed."
|
||||
throw $_ # Re-throw to trigger finally block
|
||||
}
|
||||
} else {
|
||||
Write-Error "Player matches file not found at '$playerMatchesJsonPath'. Cannot proceed."
|
||||
throw "Missing player matches file." # Throw to trigger finally block
|
||||
}
|
||||
|
||||
# --- Process Matches & Generate Kill Stats ---
|
||||
Write-Output "Starting match processing and kill stat generation..."
|
||||
$processedMatchCount = 0
|
||||
$telemetryDownloads = 0
|
||||
$telemetryCacheHits = 0
|
||||
$killStatFilesWritten = 0
|
||||
|
||||
# Extract player list (excluding special entries like 'new_win_matches')
|
||||
$playersToProcess = $allPlayerMatches | Where-Object { $_.PSObject.Properties.Name -ne 'new_win_matches' -and $_.PSObject.Properties.Name -ne 'new_loss_matches' -and $null -ne $_.playername }
|
||||
|
||||
foreach ($playerEntry in $playersToProcess) {
|
||||
$playerName = $playerEntry.playername
|
||||
if (-not $playerName) {
|
||||
Write-Warning "Skipping player entry with missing name."
|
||||
continue
|
||||
}
|
||||
|
||||
)
|
||||
$MatchStatsPlayer = @()
|
||||
foreach ($player in $playernames) {
|
||||
if ($null -eq $player) {
|
||||
if ($null -eq $playerEntry.player_matches -or -not ($playerEntry.player_matches -is [array])) {
|
||||
Write-Warning "Skipping player $playerName due to missing or invalid 'player_matches' array."
|
||||
continue
|
||||
}
|
||||
if ($GameMode) {
|
||||
$filterProperty = 'gameMode'
|
||||
}
|
||||
if ($MatchType) {
|
||||
$filterProperty = 'matchType'
|
||||
}
|
||||
$alives = (($killstats | where-object { $_.stats.playername -eq $player -and $_.stats.$filterProperty -like $typemodevalue }).deathType | where-object { $_ -eq 'alive' }).count
|
||||
$deaths = (($killstats | where-object { $_.stats.playername -eq $player -and $_.stats.$filterProperty -like $typemodevalue }).deathType | where-object { $_ -ne 'alive' }).count
|
||||
$kills = (($killstats.stats | where-object { $_.playername -eq $player -and $_.$filterProperty -like $typemodevalue }).kills | Measure-Object -sum).sum
|
||||
$dbno = (($killstats.stats | where-object { $_.playername -eq $player -and $_.$filterProperty -like $typemodevalue }).dbno | Measure-Object -sum).sum
|
||||
$humankills = (($killstats.stats | where-object { $_.playername -eq $player -and $_.$filterProperty -like $typemodevalue }).humankills | Measure-Object -sum).sum
|
||||
$player_matches = ($killstats.stats | where-object { $_.playername -eq $player -and $_.$filterProperty -like $typemodevalue }).count
|
||||
$player_wins = ($killstats | where-object { $_.stats.playername -eq $player -and $_.winplace -eq 1 -and $_.stats.$filterProperty -like $typemodevalue }).count
|
||||
$winratio_old = (($oldstats.$friendlyname | Where-Object { $_.playername -eq $player }).winratio)
|
||||
$winratio = Get-winratio -player_wins $player_wins -player_matches $player_matches
|
||||
$change = get-change -OldWinRatio $winratio_old -NewWinRatio $winratio
|
||||
$avarage_human_damage = [math]::Round((($killstats.stats | where-object { $_.playername -eq $player -and $_.$filterProperty -like $typemodevalue } | Measure-Object -Property HumanDmg -Sum).Sum / $player_matches), 2)
|
||||
|
||||
write-host $filterProperty
|
||||
write-host $typemodevalue
|
||||
write-host "Calculating for player $player"
|
||||
write-host "new winratio $winratio"
|
||||
write-host "Old winratio $winratio_old"
|
||||
write-host $change
|
||||
|
||||
$MatchStatsPlayer += [PSCustomObject]@{
|
||||
alives = $alives
|
||||
playername = $player
|
||||
deaths = $deaths
|
||||
kills = $kills
|
||||
humankills = $humankills
|
||||
matches = $player_matches
|
||||
KD_H = $humankills / $deaths
|
||||
KD_ALL = $kills / $deaths
|
||||
winratio = $winratio
|
||||
wins = $player_wins
|
||||
dbno = $dbno
|
||||
change = $change
|
||||
ahd = $avarage_human_damage
|
||||
|
||||
foreach ($match in $playerEntry.player_matches) {
|
||||
# Validate essential match data
|
||||
$matchId = $match.id
|
||||
$telemetryUrl = $match.telemetry_url
|
||||
$matchCreatedAt = $match.createdAt
|
||||
$matchGameMode = $match.gameMode
|
||||
$matchType = $match.matchType
|
||||
$matchStats = $match.stats # Player-specific stats within this match entry
|
||||
|
||||
if (-not $matchId -or -not $telemetryUrl -or -not $matchCreatedAt -or -not $matchStats) {
|
||||
Write-Warning "Skipping match for player $playerName due to missing ID, Telemetry URL, Creation Date, or Stats."
|
||||
continue
|
||||
}
|
||||
|
||||
Write-Verbose "Analyzing match $matchId for player $playerName"
|
||||
$processedMatchCount++
|
||||
$killStatFilePath = Join-Path -Path $killStatsPath -ChildPath "${matchId}_${playerName}.json"
|
||||
|
||||
if (Test-Path -Path $killStatFilePath -PathType Leaf) {
|
||||
Write-Verbose "Kill stats file already exists: $killStatFilePath"
|
||||
continue # Skip if already processed
|
||||
}
|
||||
|
||||
# Get Telemetry Data (Download or Cache)
|
||||
$telemetry = $null
|
||||
$telemetryFileName = $telemetryUrl.Split('/')[-1]
|
||||
$telemetryCacheFilePath = Join-Path -Path $telemetryCachePath -ChildPath $telemetryFileName
|
||||
|
||||
if (Test-Path -Path $telemetryCacheFilePath -PathType Leaf) {
|
||||
Write-Verbose "Loading telemetry from cache: $telemetryCacheFilePath"
|
||||
try {
|
||||
$telemetry = Get-Content -Path $telemetryCacheFilePath | ConvertFrom-Json -ErrorAction Stop
|
||||
if ($null -eq $telemetry) { Write-Warning "Failed to parse cached telemetry: $telemetryCacheFilePath" }
|
||||
else { $telemetryCacheHits++ }
|
||||
} catch {
|
||||
Write-Warning "Error reading cached telemetry '$telemetryCacheFilePath': $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
if ($null -eq $telemetry) {
|
||||
# Download telemetry if not cached or cache read failed
|
||||
Write-Output ("Downloading telemetry for match {0}: {1}" -f $matchId, $telemetryUrl)
|
||||
try {
|
||||
# Add User-Agent
|
||||
$headers = @{ 'Accept-Encoding' = 'gzip' } # Request compression
|
||||
$response = Invoke-WebRequest -Uri $telemetryUrl -Headers $headers -UseBasicParsing -ErrorAction Stop
|
||||
$telemetryContent = $response.Content # Content is automatically decompressed
|
||||
|
||||
# Save to cache
|
||||
$telemetryContent | Out-File -FilePath $telemetryCacheFilePath -Encoding UTF8 -ErrorAction SilentlyContinue
|
||||
$telemetryDownloads++
|
||||
|
||||
# Parse downloaded content
|
||||
$telemetry = $telemetryContent | ConvertFrom-Json -ErrorAction Stop
|
||||
if ($null -eq $telemetry) { Write-Warning "Failed to parse downloaded telemetry for match $matchId." }
|
||||
|
||||
} catch {
|
||||
$errorMessage = $_.Exception.Message # Assign to variable first
|
||||
Write-Warning "Failed to download or save telemetry for match $matchId. URL: $telemetryUrl. Error: $errorMessage" # Use variable in string
|
||||
# Continue to next match if telemetry fails
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
# Process Telemetry if successfully loaded/downloaded
|
||||
if ($null -ne $telemetry -and ($telemetry -is [array])) {
|
||||
Write-Verbose "Analyzing telemetry for player $playerName in match $matchId"
|
||||
# Filter relevant telemetry events once
|
||||
$relevantTelemetry = $telemetry | Where-Object { $_._T -eq 'LogPlayerTakeDamage' -or $_._T -eq 'LogPlayerKillV2' }
|
||||
|
||||
$killStatResult = Get-KillStatsFromTelemetry -playerName $playerName -telemetryEvents $relevantTelemetry -gameMode $matchGameMode -matchType $matchType
|
||||
|
||||
if ($null -ne $killStatResult) {
|
||||
# Find player's win place and death type from the original match data
|
||||
$playerMatchStats = $allPlayerMatches |
|
||||
Select-Object -ExpandProperty player_matches |
|
||||
Where-Object { $_.id -eq $matchId } |
|
||||
Select-Object -ExpandProperty stats |
|
||||
Where-Object { $_.name -eq $playerName } |
|
||||
Select-Object -First 1
|
||||
|
||||
$deathType = $playerMatchStats.deathType ?? "unknown"
|
||||
$winPlace = $playerMatchStats.winPlace ?? 0
|
||||
|
||||
$saveKillStats = @{
|
||||
matchid = $matchId
|
||||
created = $matchCreatedAt # Use original timestamp
|
||||
stats = $killStatResult
|
||||
deathType = $deathType
|
||||
winplace = $winPlace
|
||||
}
|
||||
|
||||
# Save the individual kill stat file
|
||||
try {
|
||||
$saveKillStats | ConvertTo-Json -Depth 5 | Out-File -FilePath $killStatFilePath -Encoding UTF8 -ErrorAction Stop
|
||||
Write-Output "Written kill stats file: $killStatFilePath"
|
||||
$killStatFilesWritten++
|
||||
} catch {
|
||||
Write-Warning "Failed to write kill stats file '$killStatFilePath'. Error: $($_.Exception.Message)"
|
||||
}
|
||||
} else {
|
||||
Write-Warning "Get-KillStatsFromTelemetry returned null for $playerName in match $matchId."
|
||||
}
|
||||
} else {
|
||||
Write-Warning "Telemetry data for match $matchId is null or not an array after loading/downloading."
|
||||
}
|
||||
} # End foreach match
|
||||
} # End foreach playerEntry
|
||||
Write-Output "Finished match processing. Processed: $processedMatchCount matches. Telemetry Downloads: $telemetryDownloads, Cache Hits: $telemetryCacheHits. Kill Stats Files Written: $killStatFilesWritten."
|
||||
|
||||
# --- Aggregate Kill Stats & Archive Old Files ---
|
||||
Write-Output "Aggregating kill stats and archiving old files..."
|
||||
$currentKillStats = @()
|
||||
$killStatsClanMatchesGt1 = @() # Renamed for clarity
|
||||
# $killStatsClanMatchesGt2 = @() # Not used later, commented out
|
||||
# $killStatsClanMatchesGt3 = @() # Not used later, commented out
|
||||
$archivedKillStatFiles = 0
|
||||
$processedKillStatFiles = 0
|
||||
|
||||
try {
|
||||
$matchFiles = Get-ChildItem -Path $killStatsPath -File -Filter *.json -ErrorAction SilentlyContinue
|
||||
if ($null -eq $matchFiles) {
|
||||
Write-Warning "No kill stat files found in '$killStatsPath'."
|
||||
} else {
|
||||
# Determine date threshold for archiving
|
||||
$archiveThresholdDate = (Get-Date).AddMonths($monthsBack)
|
||||
Write-Output "Archiving kill stat files older than: $archiveThresholdDate"
|
||||
|
||||
# Group files by match ID to count clan participation
|
||||
$groupedFiles = $matchFiles | Group-Object -Property { $_.Name.Split('_')[0] }
|
||||
$matchIdsWithClanGt1 = ($groupedFiles | Where-Object { $_.Count -gt 1 }).Name
|
||||
|
||||
foreach ($file in $matchFiles) {
|
||||
$processedKillStatFiles++
|
||||
try {
|
||||
$json = Get-Content -Path $file.FullName | ConvertFrom-Json -ErrorAction Stop
|
||||
if ($null -eq $json -or $null -eq $json.created -or $null -eq $json.matchid) {
|
||||
Write-Warning "Skipping invalid or incomplete kill stat file: $($file.Name)"
|
||||
continue
|
||||
}
|
||||
|
||||
# Attempt to parse the date string
|
||||
$fileDate = $null
|
||||
try { $fileDate = [datetime]$json.created } catch { Write-Warning "Could not parse date '$($json.created)' in file $($file.Name)" }
|
||||
|
||||
if ($null -ne $fileDate -and $fileDate -ge $archiveThresholdDate) {
|
||||
# Keep stats if within date range
|
||||
$currentKillStats += $json
|
||||
# Add to clan participation list if applicable
|
||||
if ($matchIdsWithClanGt1 -contains $json.matchid) {
|
||||
$killStatsClanMatchesGt1 += $json
|
||||
}
|
||||
} else {
|
||||
# Archive old file
|
||||
Write-Verbose "Archiving $($file.Name)"
|
||||
Move-Item -Path $file.FullName -Destination $archivePath -Force -ErrorAction SilentlyContinue # Continue if move fails
|
||||
if ($?) { $archivedKillStatFiles++ }
|
||||
else { Write-Warning "Failed to archive file: $($file.Name)" }
|
||||
}
|
||||
} catch {
|
||||
Write-Warning "Error processing kill stat file '$($file.Name)': $($_.Exception.Message)"
|
||||
}
|
||||
} # End foreach file
|
||||
Write-Output "Processed $processedKillStatFiles kill stat files. Archived: $archivedKillStatFiles. Kept: $($currentKillStats.Count)."
|
||||
}
|
||||
} catch {
|
||||
Write-Warning "Error reading kill stats directory '$killStatsPath': $($_.Exception.Message)"
|
||||
}
|
||||
$MatchStatsPlayer_sorted = $MatchStatsPlayer | ForEach-Object {
|
||||
$_ | Add-Member -NotePropertyName RandomKey -NotePropertyValue (Get-Random) -PassThru
|
||||
} | Sort-Object -Property $sortstat -Descending | Select-Object -Property * -ExcludeProperty RandomKey #randomize the order
|
||||
|
||||
return $MatchStatsPlayer_sorted
|
||||
}
|
||||
|
||||
|
||||
# --- Calculate Player Stats per Category ---
|
||||
Write-Output "Calculating aggregated player stats per category..."
|
||||
|
||||
# Define function locally for clarity, even if slightly redundant
|
||||
function Get-AggregatedMatchStatsPlayer {
|
||||
param (
|
||||
[Parameter(Mandatory=$true)]
|
||||
[switch]$FilterByGameMode, # Use specific parameter names
|
||||
[Parameter(Mandatory=$true)]
|
||||
[switch]$FilterByMatchType,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$FilterValue, # Value for gameMode or matchType
|
||||
[Parameter(Mandatory=$true)]
|
||||
[array]$PlayerNames,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$CategoryFriendlyName, # Key for looking up old stats
|
||||
[Parameter(Mandatory=$true)]
|
||||
[array]$KillStatsToAggregate, # The array of kill stat objects
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$SortStat,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[hashtable]$OldStatsData # Pass old stats explicitly
|
||||
)
|
||||
|
||||
$aggregatedStatsList = @()
|
||||
|
||||
# Determine the property to filter on based on parameters
|
||||
$filterProperty = $null
|
||||
if ($FilterByGameMode) { $filterProperty = 'gameMode' }
|
||||
elseif ($FilterByMatchType) { $filterProperty = 'matchType' }
|
||||
else { Write-Error "Get-AggregatedMatchStatsPlayer: Must specify -FilterByGameMode or -FilterByMatchType."; return $null }
|
||||
|
||||
foreach ($player in $PlayerNames) {
|
||||
if (-not $player) { continue } # Skip null/empty player names
|
||||
|
||||
$playerstats_event_ibr = Get-MatchStatsPlayer -GameMode -typemodevalue 'ibr' -playernames $all_player_matches.playername -friendlyname 'Intense' -killstats $killstats -sortstat 'ahd'
|
||||
$playerstats_airoyale = Get-MatchStatsPlayer -MatchType -typemodevalue 'airoyale' -playernames $all_player_matches.playername -friendlyname 'Casual' -killstats $killstats -sortstat 'ahd'
|
||||
$playerstats_official = Get-MatchStatsPlayer -MatchType -typemodevalue 'official' -playernames $all_player_matches.playername -friendlyname 'official' -killstats $killstats -sortstat 'ahd'
|
||||
$playerstats_custom = Get-MatchStatsPlayer -MatchType -typemodevalue 'custom' -playernames $all_player_matches.playername -friendlyname 'custom' -killstats $killstats -sortstat 'ahd'
|
||||
$playerstats_all = Get-MatchStatsPlayer -MatchType -typemodevalue '*' -playernames $all_player_matches.playername -friendlyname 'all' -killstats $killstats -sortstat 'ahd'
|
||||
$playerstats_ranked = Get-MatchStatsPlayer -MatchType -typemodevalue 'competitive' -playernames $all_player_matches.playername -friendlyname 'Ranked' -killstats $killstats -sortstat 'ahd'
|
||||
# Filter kill stats for the current player and category
|
||||
$playerKillStats = $KillStatsToAggregate | Where-Object {
|
||||
$_.stats -ne $null -and
|
||||
$_.stats.playername -eq $player -and
|
||||
$_.stats.$filterProperty -like $FilterValue # Use -like for wildcard support if needed (e.g., '*')
|
||||
}
|
||||
|
||||
$playerMatchCount = $playerKillStats.Count
|
||||
if ($playerMatchCount -eq 0) { continue } # Skip player if no stats in this category
|
||||
|
||||
$playerstats_airoyale_clan_gt_1 = Get-MatchStatsPlayer -MatchType -typemodevalue 'airoyale' -playernames $all_player_matches.playername -friendlyname 'Casual' -killstats $killstats_clan_matches_gt_1 -sortstat 'ahd'
|
||||
# Aggregate stats using Measure-Object where possible
|
||||
$totalWins = ($playerKillStats | Where-Object { $_.winplace -eq 1 }).Count
|
||||
$totalDeaths = ($playerKillStats | Where-Object { $_.deathType -ne 'alive' }).Count # Assuming 'alive' means survived
|
||||
$totalKills = ($playerKillStats.stats.kills | Measure-Object -Sum -ErrorAction SilentlyContinue).Sum
|
||||
$totalHumanKills = ($playerKillStats.stats.humankills | Measure-Object -Sum -ErrorAction SilentlyContinue).Sum
|
||||
$totalDbno = ($playerKillStats.stats.dbno | Measure-Object -Sum -ErrorAction SilentlyContinue).Sum
|
||||
$totalHumanDmg = ($playerKillStats.stats.HumanDmg | Measure-Object -Sum -ErrorAction SilentlyContinue).Sum
|
||||
|
||||
$playerstats_custom = $playerstats_custom | Sort-Object winratio -Descending
|
||||
# Calculate Ratios (Handle division by zero)
|
||||
$kdHuman = if ($totalDeaths -gt 0) { [math]::Round($totalHumanKills / $totalDeaths, 2) } else { $totalHumanKills } # Or "Infinity" string
|
||||
$kdAll = if ($totalDeaths -gt 0) { [math]::Round($totalKills / $totalDeaths, 2) } else { $totalKills } # Or "Infinity" string
|
||||
$avgHumanDmg = if ($playerMatchCount -gt 0) { [math]::Round($totalHumanDmg / $playerMatchCount, 2) } else { 0.0 }
|
||||
|
||||
# Calculate Win Ratio and Change
|
||||
$currentWinRatio = Get-WinRatio -playerWins $totalWins -playerMatches $playerMatchCount
|
||||
$oldWinRatio = $null
|
||||
# Safely access old stats
|
||||
if ($OldStatsData.ContainsKey($CategoryFriendlyName)) {
|
||||
$oldCategoryStats = $OldStatsData[$CategoryFriendlyName]
|
||||
$playerOldStat = $oldCategoryStats | Where-Object { $_.playername -eq $player } | Select-Object -First 1
|
||||
if ($playerOldStat -and $playerOldStat.PSObject.Properties.Name -contains 'winratio') {
|
||||
# Attempt conversion, default to 0.0 if fails
|
||||
try { $oldWinRatio = [double]$playerOldStat.winratio } catch { $oldWinRatio = 0.0 }
|
||||
}
|
||||
}
|
||||
$winRatioChange = Get-Change -OldWinRatio $oldWinRatio -NewWinRatio $currentWinRatio
|
||||
|
||||
$currentDateTime = Get-Date
|
||||
Write-Verbose "Stats for $player [$CategoryFriendlyName]: Matches=$playerMatchCount, Wins=$totalWins, Deaths=$totalDeaths, Kills=$totalKills, HKills=$totalHumanKills, AHD=$avgHumanDmg, Win%=$currentWinRatio, Change=$winRatioChange"
|
||||
|
||||
# Get current timezone
|
||||
$currentTimezone = (Get-TimeZone).Id
|
||||
$aggregatedStatsList += [PSCustomObject]@{
|
||||
playername = $player
|
||||
deaths = $totalDeaths
|
||||
kills = $totalKills
|
||||
humankills = $totalHumanKills
|
||||
matches = $playerMatchCount
|
||||
KD_H = $kdHuman
|
||||
KD_ALL = $kdAll
|
||||
winratio = $currentWinRatio
|
||||
wins = $totalWins
|
||||
dbno = $totalDbno
|
||||
change = $winRatioChange # Store the calculated change
|
||||
ahd = $avgHumanDmg
|
||||
}
|
||||
} # End foreach player
|
||||
|
||||
# Format and parse the information into a string
|
||||
$formattedString = "$currentDateTime - Time Zone: $currentTimezone"
|
||||
# Sort the results
|
||||
if ($aggregatedStatsList.Count -gt 0 -and $SortStat) {
|
||||
try {
|
||||
# Add random key for stable sort behavior if primary sort keys are equal
|
||||
$aggregatedStatsList = $aggregatedStatsList | ForEach-Object {
|
||||
$_ | Add-Member -NotePropertyName RandomKey -NotePropertyValue (Get-Random) -PassThru
|
||||
} | Sort-Object -Property $SortStat, RandomKey -Descending | Select-Object -Property * -ExcludeProperty RandomKey
|
||||
} catch {
|
||||
Write-Warning "Failed to sort aggregated stats by '$SortStat'. Error: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
return $aggregatedStatsList
|
||||
} # End function Get-AggregatedMatchStatsPlayer
|
||||
|
||||
# Output the formatted string
|
||||
# Get list of unique player names from the loaded match data
|
||||
$uniquePlayerNames = ($playersToProcess.playername | Select-Object -Unique)
|
||||
|
||||
$playerstats = [PSCustomObject]@{
|
||||
all = $playerstats_all
|
||||
clan_casual = $playerstats_airoyale_clan_gt_1
|
||||
Intense = $playerstats_event_ibr
|
||||
Casual = $playerstats_airoyale
|
||||
official = $playerstats_official
|
||||
custom = $playerstats_custom
|
||||
updated = $formattedString
|
||||
Ranked = $playerstats_ranked
|
||||
# Calculate stats for each category
|
||||
$playerStatsEventIbr = Get-AggregatedMatchStatsPlayer -FilterByGameMode -FilterValue 'ibr' -PlayerNames $uniquePlayerNames -CategoryFriendlyName 'Intense' -KillStatsToAggregate $currentKillStats -SortStat 'ahd' -OldStatsData $oldStats
|
||||
$playerStatsAiRoyale = Get-AggregatedMatchStatsPlayer -FilterByMatchType -FilterValue 'airoyale' -PlayerNames $uniquePlayerNames -CategoryFriendlyName 'Casual' -KillStatsToAggregate $currentKillStats -SortStat 'ahd' -OldStatsData $oldStats
|
||||
$playerStatsOfficial = Get-AggregatedMatchStatsPlayer -FilterByMatchType -FilterValue 'official' -PlayerNames $uniquePlayerNames -CategoryFriendlyName 'official' -KillStatsToAggregate $currentKillStats -SortStat 'ahd' -OldStatsData $oldStats
|
||||
$playerStatsCustom = Get-AggregatedMatchStatsPlayer -FilterByMatchType -FilterValue 'custom' -PlayerNames $uniquePlayerNames -CategoryFriendlyName 'custom' -KillStatsToAggregate $currentKillStats -SortStat 'ahd' -OldStatsData $oldStats
|
||||
$playerStatsAll = Get-AggregatedMatchStatsPlayer -FilterByMatchType -FilterValue '*' -PlayerNames $uniquePlayerNames -CategoryFriendlyName 'all' -KillStatsToAggregate $currentKillStats -SortStat 'ahd' -OldStatsData $oldStats
|
||||
$playerStatsRanked = Get-AggregatedMatchStatsPlayer -FilterByMatchType -FilterValue 'competitive' -PlayerNames $uniquePlayerNames -CategoryFriendlyName 'Ranked' -KillStatsToAggregate $currentKillStats -SortStat 'ahd' -OldStatsData $oldStats
|
||||
$playerStatsAiRoyaleClanGt1 = Get-AggregatedMatchStatsPlayer -FilterByMatchType -FilterValue 'airoyale' -PlayerNames $uniquePlayerNames -CategoryFriendlyName 'Casual' -KillStatsToAggregate $killStatsClanMatchesGt1 -SortStat 'ahd' -OldStatsData $oldStats # Use filtered killstats
|
||||
|
||||
}
|
||||
# Apply specific sorting if needed (e.g., custom by winratio)
|
||||
if ($playerStatsCustom) {
|
||||
$playerStatsCustom = $playerStatsCustom | Sort-Object winratio -Descending
|
||||
}
|
||||
|
||||
write-output "Writing file"
|
||||
($playerstats | convertto-json) | out-file "$scriptroot/../data/player_last_stats.json"
|
||||
# --- Save Aggregated Stats ---
|
||||
Write-Output "Saving aggregated player stats..."
|
||||
$currentDateTime = Get-Date
|
||||
$currentTimezone = (Get-TimeZone).Id
|
||||
$formattedString = "$currentDateTime - Time Zone: $currentTimezone"
|
||||
|
||||
$playerStatsOutput = [PSCustomObject]@{
|
||||
all = $playerStatsAll
|
||||
clan_casual = $playerStatsAiRoyaleClanGt1
|
||||
Intense = $playerStatsEventIbr
|
||||
Casual = $playerStatsAiRoyale
|
||||
official = $playerStatsOfficial
|
||||
custom = $playerStatsCustom
|
||||
updated = $formattedString
|
||||
Ranked = $playerStatsRanked
|
||||
}
|
||||
|
||||
$date = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
|
||||
$filenameDate = ($date -replace ":", "-")
|
||||
write-output "writing to file : $scriptroot/../data/archive/$($filenameDate)_player_last_stats.json"
|
||||
($playerstats | convertto-json) | out-file "$scriptroot/../data/archive/$($filenameDate)_player_last_stats.json"
|
||||
# Save to current stats file
|
||||
try {
|
||||
$playerStatsOutput | ConvertTo-Json -Depth 10 | Out-File -FilePath $lastStatsJsonPath -Encoding UTF8 -ErrorAction Stop
|
||||
Write-Output "Aggregated stats saved to '$lastStatsJsonPath'"
|
||||
} catch {
|
||||
Write-Warning "Failed to save aggregated stats to '$lastStatsJsonPath'. Error: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
# Save to archive file
|
||||
$archiveFileNameDate = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH-mm-ssZ") -replace ":", "-"
|
||||
$archiveFilePath = Join-Path -Path $archiveDir -ChildPath "${archiveFileNameDate}_player_last_stats.json"
|
||||
try {
|
||||
Write-Output "Archiving aggregated stats to: $archiveFilePath"
|
||||
$playerStatsOutput | ConvertTo-Json -Depth 10 | Out-File -FilePath $archiveFilePath -Encoding UTF8 -ErrorAction Stop
|
||||
} catch {
|
||||
Write-Warning "Failed to archive aggregated stats to '$archiveFilePath'. Error: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
write-output "Cleaning cache"
|
||||
# --- Clean Telemetry Cache ---
|
||||
Write-Output "Cleaning telemetry cache..."
|
||||
try {
|
||||
# Get telemetry URLs from the *currently loaded* player matches data
|
||||
$activeTelemetryUrls = ($allPlayerMatches | Select-Object -ExpandProperty player_matches).telemetry_url | Select-Object -Unique
|
||||
$filesToKeep = $activeTelemetryUrls | ForEach-Object { if ($_) { $_.Split('/')[-1] } } # Get just the filenames
|
||||
|
||||
$files_keep = (($all_player_matches).player_matches.telemetry_url | Select-Object -Unique) | ForEach-Object { $_.split("/")[-1] }
|
||||
$files_cache = (get-childitem "$scriptroot/../data/telemetry_cache/").name
|
||||
$cachedFiles = Get-ChildItem -Path $telemetryCachePath -File -ErrorAction SilentlyContinue
|
||||
|
||||
if ($cachedFiles) {
|
||||
$filesToRemove = Compare-Object -ReferenceObject $filesToKeep -DifferenceObject $cachedFiles.Name -PassThru | Where-Object { $_ -ne $null }
|
||||
|
||||
$removedCount = 0
|
||||
foreach ($fileToRemoveName in $filesToRemove) {
|
||||
$fileToRemovePath = Join-Path -Path $telemetryCachePath -ChildPath $fileToRemoveName
|
||||
Write-Verbose "Removing cached telemetry file: $fileToRemovePath"
|
||||
Remove-Item -Path $fileToRemovePath -Force -ErrorAction SilentlyContinue
|
||||
if ($?) { $removedCount++ }
|
||||
else { Write-Warning "Failed to remove cache file: $fileToRemovePath" }
|
||||
}
|
||||
Write-Output "Telemetry cache cleanup complete. Removed $removedCount files."
|
||||
} else {
|
||||
Write-Output "Telemetry cache directory is empty or inaccessible."
|
||||
}
|
||||
} catch {
|
||||
Write-Warning "Error during telemetry cache cleanup: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
$difference = (Compare-Object -ReferenceObject $files_keep -DifferenceObject $files_cache | Where-Object { $_.SideIndicator -eq "=>" }).InputObject
|
||||
Write-Output "Match parsing and aggregation complete."
|
||||
|
||||
foreach ($file in $difference) {
|
||||
write-output "removing $scriptroot/../data/telemetry_cache/$file"
|
||||
Remove-Item -Path "$scriptroot/../data/telemetry_cache/$file"
|
||||
}
|
||||
write-output "Operation complete"
|
||||
remove-lock
|
||||
Stop-Transcript
|
||||
} # End Main Try Block
|
||||
finally {
|
||||
# --- Cleanup ---
|
||||
Write-Output "Script finished at $(Get-Date)."
|
||||
Remove-Lock # Ensure lock is always removed
|
||||
Stop-Transcript
|
||||
}
|
||||
|
|
@ -1,50 +1,178 @@
|
|||
if($PSScriptRoot.length -eq 0){
|
||||
$scriptroot = Get-Location
|
||||
}else{
|
||||
$scriptroot = $PSScriptRoot
|
||||
}
|
||||
. $scriptroot\..\includes\ps1\lockfile.ps1
|
||||
new-lock -by "update_clan"
|
||||
# Read the content of the file as a single string
|
||||
$fileContent = Get-Content -Path "$scriptroot/../config/config.php" -Raw
|
||||
# --- Script Setup ---
|
||||
# Using Unicode BOM (Byte Order Mark) can sometimes cause issues, ensure file is saved as UTF-8 without BOM if problems arise.
|
||||
Start-Transcript -Path '/var/log/dtch/update_clan.log' -Append
|
||||
Write-Output "Starting update_clan script at $(Get-Date)"
|
||||
Write-Output "Running from: $(Get-Location)"
|
||||
|
||||
# Use regex to match the apiKey value
|
||||
if ($fileContent -match "\`$apiKey\s*=\s*\'([^\']+)\'") {
|
||||
$apiKey = $matches[1]
|
||||
}else {
|
||||
Write-Output "API Key not found"
|
||||
}
|
||||
|
||||
if ($fileContent -match "\`$clanid\s*=\s*\'([^\']+)\'") {
|
||||
$clanid = $matches[1]
|
||||
# Determine script root directory reliably
|
||||
if ($PSScriptRoot) {
|
||||
$scriptRoot = $PSScriptRoot
|
||||
} else {
|
||||
Write-Output "No clanid found in $configPath"
|
||||
# Fallback for environments where $PSScriptRoot is not defined (e.g., ISE)
|
||||
$scriptRoot = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition
|
||||
Write-Warning "PSScriptRoot not defined, using calculated path: $scriptRoot"
|
||||
}
|
||||
Write-Output "Script root identified as: $scriptRoot"
|
||||
|
||||
# Define paths using Join-Path for robustness
|
||||
$includesPath = Join-Path -Path $scriptRoot -ChildPath "..\includes\ps1"
|
||||
$configPath = Join-Path -Path $scriptRoot -ChildPath "..\config"
|
||||
$dataPath = Join-Path -Path $scriptRoot -ChildPath "..\data"
|
||||
|
||||
# Ensure data directory exists (copied from update_clan_members.ps1 for consistency)
|
||||
if (-not (Test-Path -Path $dataPath -PathType Container)) {
|
||||
Write-Warning "Data directory not found at '$dataPath'. Attempting to create."
|
||||
try {
|
||||
New-Item -Path $dataPath -ItemType Directory -Force -ErrorAction Stop | Out-Null
|
||||
Write-Output "Successfully created data directory."
|
||||
} catch {
|
||||
Write-Error "Failed to create data directory '$dataPath'. Please check permissions. Error: $($_.Exception.Message)"
|
||||
Stop-Transcript
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# --- Locking ---
|
||||
$lockFilePath = Join-Path -Path $includesPath -ChildPath "lockfile.ps1"
|
||||
if (-not (Test-Path -Path $lockFilePath -PathType Leaf)) {
|
||||
Write-Error "Lockfile script not found at '$lockFilePath'. Cannot proceed."
|
||||
Stop-Transcript
|
||||
exit 1
|
||||
}
|
||||
. $lockFilePath
|
||||
New-Lock -by "update_clan" -ErrorAction Stop # Stop if locking fails
|
||||
|
||||
# --- Configuration Loading ---
|
||||
$apiKey = $null
|
||||
$clanId = $null
|
||||
|
||||
# Load API Key and Clan ID from config.php
|
||||
$phpConfigPath = Join-Path -Path $configPath -ChildPath "config.php"
|
||||
if (Test-Path -Path $phpConfigPath -PathType Leaf) {
|
||||
try {
|
||||
$fileContent = Get-Content -Path $phpConfigPath -Raw -ErrorAction Stop
|
||||
|
||||
# Corrected regex for apiKey
|
||||
if ($fileContent -match '^\s*\$apiKey\s*=\s*''([^'']+)''') {
|
||||
$apiKey = $matches[1]
|
||||
Write-Output "API Key loaded successfully."
|
||||
} else {
|
||||
Write-Warning "API Key pattern not found in '$phpConfigPath'."
|
||||
}
|
||||
|
||||
# Corrected regex for clanid
|
||||
if ($fileContent -match '^\s*\$clanid\s*=\s*''([^'']+)''') {
|
||||
$clanId = $matches[1]
|
||||
Write-Output "Clan ID loaded successfully."
|
||||
} else {
|
||||
Write-Warning "Clan ID pattern not found in '$phpConfigPath'."
|
||||
}
|
||||
} catch {
|
||||
Write-Warning "Failed to read '$phpConfigPath'. Error: $($_.Exception.Message)"
|
||||
}
|
||||
} else {
|
||||
Write-Warning "Config file not found at '$phpConfigPath'."
|
||||
}
|
||||
|
||||
# Validate required config
|
||||
if (-not $apiKey) {
|
||||
Write-Error "API Key could not be loaded. Cannot proceed."
|
||||
Remove-Lock
|
||||
Stop-Transcript
|
||||
exit 1
|
||||
}
|
||||
if (-not $clanId) {
|
||||
Write-Error "Clan ID could not be loaded. Cannot proceed."
|
||||
Remove-Lock
|
||||
Stop-Transcript
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Helper Function for API Calls (Copied from update_clan_members.ps1 for self-containment) ---
|
||||
function Invoke-PubgApi {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$Uri,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[hashtable]$Headers,
|
||||
[int]$RetryCount = 1,
|
||||
[int]$RetryDelaySeconds = 61
|
||||
)
|
||||
|
||||
for ($attempt = 1; $attempt -le ($RetryCount + 1); $attempt++) {
|
||||
try {
|
||||
Write-Verbose "Attempting API call (Attempt $($attempt)): $Uri"
|
||||
$response = Invoke-RestMethod -Uri $Uri -Method GET -Headers $Headers -ErrorAction Stop
|
||||
if ($null -ne $response) {
|
||||
Write-Verbose "API call successful."
|
||||
return $response
|
||||
} else {
|
||||
Write-Warning "API call to $Uri returned null or empty response."
|
||||
return $null
|
||||
}
|
||||
} catch {
|
||||
$statusCode = $_.Exception.Response.StatusCode.value__
|
||||
$errorMessage = $_.Exception.Message
|
||||
Write-Warning "API call failed (Attempt $($attempt)). Status: $statusCode. Error: $errorMessage"
|
||||
|
||||
if ($attempt -le $RetryCount -and $statusCode -eq 429) {
|
||||
Write-Warning "Rate limit hit. Sleeping for $RetryDelaySeconds seconds before retry..."
|
||||
Start-Sleep -Seconds $RetryDelaySeconds
|
||||
} elseif ($attempt -gt $RetryCount) {
|
||||
Write-Error "API call failed after $($attempt) attempts. URI: $Uri. Last Error: $errorMessage"
|
||||
return $null
|
||||
} else {
|
||||
Write-Error "Non-retryable API error. URI: $Uri. Error: $errorMessage"
|
||||
return $null
|
||||
}
|
||||
}
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
# --- Get Clan Information ---
|
||||
Write-Output "Fetching clan information for ID: $clanId"
|
||||
$headers = @{
|
||||
'accept' = 'application/vnd.api+json'
|
||||
'Authorization' = "$apiKey"
|
||||
'Authorization' = "Bearer $apiKey" # Standard practice
|
||||
}
|
||||
try {
|
||||
$claninfo = Invoke-RestMethod -Uri "https://api.pubg.com/shards/steam/clans/$clanid" -Method GET -Headers $headers
|
||||
} catch {
|
||||
write-output "sleeping for 61 sec"
|
||||
start-sleep -Seconds 61
|
||||
$claninfo = Invoke-RestMethod -Uri "https://api.pubg.com/shards/steam/clans/$clanid" -Method GET -Headers $headers
|
||||
$apiUrl = "https://api.pubg.com/shards/steam/clans/$clanId"
|
||||
|
||||
$clanInfoResponse = Invoke-PubgApi -Uri $apiUrl -Headers $headers
|
||||
|
||||
# --- Process and Save Clan Data ---
|
||||
if ($null -ne $clanInfoResponse -and $null -ne $clanInfoResponse.data.attributes) {
|
||||
Write-Output "Successfully retrieved clan information."
|
||||
|
||||
# Create PS Custom Object from attributes
|
||||
$clanData = [PSCustomObject]$clanInfoResponse.data.attributes
|
||||
|
||||
# Add update timestamp
|
||||
$currentDateTime = Get-Date
|
||||
$currentTimezone = (Get-TimeZone).Id
|
||||
$formattedString = "$currentDateTime - Time Zone: $currentTimezone"
|
||||
$clanData | Add-Member -Name "updated" -MemberType NoteProperty -Value $formattedString
|
||||
Write-Output "Added update timestamp: $formattedString"
|
||||
|
||||
# Save clan data to JSON file
|
||||
$clanInfoJsonPath = Join-Path -Path $dataPath -ChildPath "claninfo.json"
|
||||
try {
|
||||
$clanData | ConvertTo-Json -Depth 100 | Out-File -FilePath $clanInfoJsonPath -Encoding UTF8 -ErrorAction Stop
|
||||
Write-Output "Clan info saved to '$clanInfoJsonPath'"
|
||||
|
||||
# Output JSON to transcript for verification (optional)
|
||||
# Write-Output "Saved Data:"
|
||||
# $clanData | ConvertTo-Json -Depth 100 | Write-Output
|
||||
|
||||
} catch {
|
||||
Write-Error "Failed to save clan info to '$clanInfoJsonPath'. Error: $($_.Exception.Message)"
|
||||
}
|
||||
} else {
|
||||
Write-Error "Failed to retrieve valid clan information from API for ID: $clanId"
|
||||
# Consider if script should exit or continue
|
||||
}
|
||||
# Get current date and time
|
||||
$currentDateTime = Get-Date
|
||||
|
||||
# Get current timezone
|
||||
$currentTimezone = (Get-TimeZone).Id
|
||||
|
||||
# Format and parse the information into a string
|
||||
$formattedString = "$currentDateTime - Time Zone: $currentTimezone"
|
||||
# Output the formatted string
|
||||
|
||||
|
||||
[PSCustomObject]$clandata = $claninfo.data.attributes
|
||||
$clandata | Add-Member -Name "updated" -MemberType NoteProperty -Value $formattedString
|
||||
$clandata | convertto-json -Depth 100 | out-file "$scriptroot/../data/claninfo.json"
|
||||
|
||||
$clandata | convertto-json -Depth 100
|
||||
remove-lock
|
||||
# --- Cleanup ---
|
||||
Write-Output "Script finished at $(Get-Date)."
|
||||
Remove-Lock
|
||||
Stop-Transcript
|
||||
|
|
@ -1,202 +1,335 @@
|
|||
Start-Transcript -Path '/var/log/dtch/update_clan_members.log' -Append
|
||||
Write-Output 'Running from'
|
||||
(Get-Location).path
|
||||
# --- Script Setup ---
|
||||
# Using Unicode BOM (Byte Order Mark) can sometimes cause issues, ensure file is saved as UTF-8 without BOM if problems arise.
|
||||
Start-Transcript -Path '/var/log/dtch/update_clan_members.log' -Append
|
||||
Write-Output "Starting update_clan_members script at $(Get-Date)"
|
||||
Write-Output "Running from: $(Get-Location)"
|
||||
|
||||
if ($PSScriptRoot.length -eq 0) {
|
||||
$scriptroot = Get-Location
|
||||
}
|
||||
else {
|
||||
$scriptroot = $PSScriptRoot
|
||||
# Determine script root directory reliably
|
||||
if ($PSScriptRoot) {
|
||||
$scriptRoot = $PSScriptRoot
|
||||
} else {
|
||||
# Fallback for environments where $PSScriptRoot is not defined (e.g., ISE)
|
||||
$scriptRoot = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition
|
||||
Write-Warning "PSScriptRoot not defined, using calculated path: $scriptRoot"
|
||||
}
|
||||
Write-Output "Script root identified as: $scriptRoot"
|
||||
|
||||
. $scriptroot\..\includes\ps1\lockfile.ps1
|
||||
new-lock -by "update_clan_members"
|
||||
# Define paths using Join-Path for robustness
|
||||
$includesPath = Join-Path -Path $scriptRoot -ChildPath "..\includes\ps1"
|
||||
$configPath = Join-Path -Path $scriptRoot -ChildPath "..\config"
|
||||
$dataPath = Join-Path -Path $scriptRoot -ChildPath "..\data"
|
||||
|
||||
# Read the content of the file as a single string
|
||||
$fileContent = Get-Content -Path "$scriptroot/../config/config.php" -Raw
|
||||
|
||||
# Use regex to match the apiKey value
|
||||
if ($fileContent -match "\`$apiKey\s*=\s*\'([^\']+)\'") {
|
||||
$apiKey = $matches[1]
|
||||
}
|
||||
else {
|
||||
Write-Output "API Key not found"
|
||||
}
|
||||
|
||||
|
||||
$clanMembersArray = (Get-Content "$scriptroot/../config/clanmembers.json" | ConvertFrom-Json).clanMembers
|
||||
|
||||
$clanmemberchunks = @()
|
||||
$chunk = @()
|
||||
$chunksize = 10
|
||||
$i = 0
|
||||
|
||||
foreach ($member in $clanMembersArray) {
|
||||
$chunk += $member
|
||||
if ($chunk.Count -eq $chunksize) {
|
||||
$clanmemberchunks += @{ "Chunk$i" = $chunk }
|
||||
$chunk = @()
|
||||
$i++
|
||||
# Ensure data directory exists
|
||||
if (-not (Test-Path -Path $dataPath -PathType Container)) {
|
||||
Write-Warning "Data directory not found at '$dataPath'. Attempting to create."
|
||||
try {
|
||||
New-Item -Path $dataPath -ItemType Directory -Force -ErrorAction Stop | Out-Null
|
||||
Write-Output "Successfully created data directory."
|
||||
} catch {
|
||||
Write-Error "Failed to create data directory '$dataPath'. Please check permissions. Error: $($_.Exception.Message)"
|
||||
Stop-Transcript
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# Add any remaining members to the last chunk
|
||||
if ($chunk.Count -gt 0) {
|
||||
$clanmemberchunks += @{ "Chunk$i" = $chunk }
|
||||
# --- Locking ---
|
||||
$lockFilePath = Join-Path -Path $includesPath -ChildPath "lockfile.ps1"
|
||||
if (-not (Test-Path -Path $lockFilePath -PathType Leaf)) {
|
||||
Write-Error "Lockfile script not found at '$lockFilePath'. Cannot proceed."
|
||||
Stop-Transcript
|
||||
exit 1
|
||||
}
|
||||
$clanMembers = $clanMembersArray -join ','
|
||||
. $lockFilePath
|
||||
New-Lock -by "update_clan_members" -ErrorAction Stop # Stop if locking fails
|
||||
|
||||
# --- Configuration Loading ---
|
||||
$apiKey = $null
|
||||
$clanMembersArray = @()
|
||||
|
||||
# Load API Key from config.php
|
||||
$phpConfigPath = Join-Path -Path $configPath -ChildPath "config.php"
|
||||
if (Test-Path -Path $phpConfigPath -PathType Leaf) {
|
||||
try {
|
||||
$fileContent = Get-Content -Path $phpConfigPath -Raw -ErrorAction Stop
|
||||
# Corrected regex: Match literal '$apiKey', whitespace, '=', whitespace, single quote, capture content, single quote.
|
||||
if ($fileContent -match '^\s*\$apiKey\s*=\s*''([^'']+)''') {
|
||||
$apiKey = $matches[1]
|
||||
Write-Output "API Key loaded successfully."
|
||||
} else {
|
||||
Write-Warning "API Key pattern not found in '$phpConfigPath'."
|
||||
}
|
||||
} catch {
|
||||
Write-Warning "Failed to read '$phpConfigPath'. Error: $($_.Exception.Message)"
|
||||
}
|
||||
} else {
|
||||
Write-Warning "Config file not found at '$phpConfigPath'."
|
||||
}
|
||||
|
||||
if (-not $apiKey) {
|
||||
Write-Error "API Key could not be loaded. Cannot proceed without API Key."
|
||||
Remove-Lock
|
||||
Stop-Transcript
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Load Clan Members from clanmembers.json
|
||||
$clanMembersJsonPath = Join-Path -Path $configPath -ChildPath "clanmembers.json"
|
||||
if (Test-Path -Path $clanMembersJsonPath -PathType Leaf) {
|
||||
try {
|
||||
$clanMembersData = Get-Content -Path $clanMembersJsonPath -Raw | ConvertFrom-Json -ErrorAction Stop
|
||||
if ($clanMembersData -is [PSCustomObject] -and $clanMembersData.PSObject.Properties.Name -contains 'clanMembers' -and $clanMembersData.clanMembers -is [array]) {
|
||||
$clanMembersArray = $clanMembersData.clanMembers
|
||||
Write-Output "Clan members loaded successfully. Count: $($clanMembersArray.Count)"
|
||||
} else {
|
||||
Write-Warning "Invalid structure in '$clanMembersJsonPath'. Expected an object with a 'clanMembers' array."
|
||||
}
|
||||
} catch {
|
||||
Write-Warning "Failed to read or parse '$clanMembersJsonPath'. Error: $($_.Exception.Message)"
|
||||
}
|
||||
} else {
|
||||
Write-Warning "Clan members file not found at '$clanMembersJsonPath'."
|
||||
}
|
||||
|
||||
if ($clanMembersArray.Count -eq 0) {
|
||||
Write-Error "No clan members loaded. Cannot proceed."
|
||||
Remove-Lock
|
||||
Stop-Transcript
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Helper Function for API Calls ---
|
||||
function Invoke-PubgApi {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$Uri,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[hashtable]$Headers,
|
||||
[int]$RetryCount = 1,
|
||||
[int]$RetryDelaySeconds = 61
|
||||
)
|
||||
|
||||
for ($attempt = 1; $attempt -le ($RetryCount + 1); $attempt++) {
|
||||
try {
|
||||
Write-Verbose "Attempting API call (Attempt $($attempt)): $Uri"
|
||||
$response = Invoke-RestMethod -Uri $Uri -Method GET -Headers $Headers -ErrorAction Stop
|
||||
# Basic validation: Check if response is not null
|
||||
if ($null -ne $response) {
|
||||
Write-Verbose "API call successful."
|
||||
return $response
|
||||
} else {
|
||||
Write-Warning "API call to $Uri returned null or empty response."
|
||||
# Decide if null response is an error or expected empty result
|
||||
return $null # Or handle as error if appropriate
|
||||
}
|
||||
} catch {
|
||||
$statusCode = $_.Exception.Response.StatusCode.value__
|
||||
$errorMessage = $_.Exception.Message
|
||||
Write-Warning "API call failed (Attempt $($attempt)). Status: $statusCode. Error: $errorMessage"
|
||||
|
||||
# Check for rate limit (429) or other retryable errors if needed
|
||||
if ($attempt -le $RetryCount -and $statusCode -eq 429) {
|
||||
Write-Warning "Rate limit hit. Sleeping for $RetryDelaySeconds seconds before retry..."
|
||||
Start-Sleep -Seconds $RetryDelaySeconds
|
||||
} elseif ($attempt -gt $RetryCount) {
|
||||
Write-Error "API call failed after $($attempt) attempts. URI: $Uri. Last Error: $errorMessage"
|
||||
# Re-throw the exception or return null/specific error object
|
||||
# throw $_ # Re-throw the last exception to halt script if critical
|
||||
return $null # Return null to allow script to potentially continue or handle missing data
|
||||
} else {
|
||||
# Handle other non-retryable errors immediately
|
||||
Write-Error "Non-retryable API error. URI: $Uri. Error: $errorMessage"
|
||||
return $null
|
||||
}
|
||||
}
|
||||
}
|
||||
# Should not be reached if logic is correct, but return null just in case
|
||||
return $null
|
||||
}
|
||||
|
||||
|
||||
# --- Get Player Information ---
|
||||
Write-Output "Fetching player information..."
|
||||
$headers = @{
|
||||
'accept' = 'application/vnd.api+json'
|
||||
'Authorization' = "$apiKey"
|
||||
'Authorization' = "Bearer $apiKey" # Standard practice to include "Bearer"
|
||||
}
|
||||
|
||||
|
||||
$playerinfo = @()
|
||||
foreach ($key in $clanmemberchunks.keys) {
|
||||
|
||||
$clanMembers = $clanmemberchunks.$key -join ','
|
||||
$clanMembers
|
||||
try {
|
||||
$playerinfo += Invoke-RestMethod -Uri "https://api.pubg.com/shards/steam/players?filter[playerNames]=$clanMembers" -Method GET -Headers $headers
|
||||
}
|
||||
catch {
|
||||
write-output 'Sleeping for 61 seconds'
|
||||
start-sleep -Seconds 61
|
||||
$playerinfo += Invoke-RestMethod -Uri "https://api.pubg.com/shards/steam/players?filter[playerNames]=$clanMembers" -Method GET -Headers $headers
|
||||
}
|
||||
|
||||
}
|
||||
$playerinfo.data | convertto-json -depth 100 | Out-File "$scriptroot/../data/player_data.json"
|
||||
$playerList = @()
|
||||
$playerinfo.data | ForEach-Object {
|
||||
$playerObject = [PSCustomObject]@{
|
||||
PlayerName = $_.attributes.name
|
||||
PlayerID = $_.id
|
||||
}
|
||||
$playerList += $playerObject
|
||||
# Chunk clan members for API query (max 10 per request)
|
||||
$clanMemberChunks = @()
|
||||
$chunkSize = 10
|
||||
for ($i = 0; $i -lt $clanMembersArray.Count; $i += $chunkSize) {
|
||||
$endIndex = [System.Math]::Min($i + $chunkSize - 1, $clanMembersArray.Count - 1)
|
||||
$clanMemberChunks += ,($clanMembersArray[$i..$endIndex]) # Use comma to ensure it's always an array of arrays
|
||||
}
|
||||
Write-Output "Split clan members into $($clanMemberChunks.Count) chunks."
|
||||
|
||||
# Display the list
|
||||
$playerList
|
||||
|
||||
|
||||
|
||||
$playerChunks = @{}
|
||||
$chunk = @()
|
||||
$chunksize = 10
|
||||
$i = 0
|
||||
|
||||
foreach ($player in $playerList) {
|
||||
$chunkName = "Chunk$i"
|
||||
$chunk += $player
|
||||
if ($chunk.Count -eq $chunksize) {
|
||||
$playerChunks[$chunkName] = $chunk
|
||||
$chunk = @()
|
||||
$i++
|
||||
}
|
||||
}
|
||||
|
||||
# Add any remaining players to the last chunk
|
||||
if ($chunk.Count -gt 0) {
|
||||
$playerChunks["Chunk$i"] = $chunk
|
||||
}
|
||||
|
||||
$playeridstringarray = @()
|
||||
foreach ($key in $playerChunks.keys) {
|
||||
|
||||
$playeridstringarray += $playerChunks.$key.PlayerID -join ','
|
||||
}
|
||||
|
||||
$playermodes = @(
|
||||
"solo",
|
||||
"duo",
|
||||
"squad",
|
||||
"solo-fpp",
|
||||
"duo-fpp",
|
||||
"squad-fpp"
|
||||
)
|
||||
# Initialize the master hashtable
|
||||
$lifetimestats = @{}
|
||||
foreach ($playeridstring in $playeridstringarray) {
|
||||
foreach ($playmode in $playermodes) {
|
||||
# Fetch stats for the current playmode
|
||||
$allPlayerInfoData = @()
|
||||
foreach ($chunk in $clanMemberChunks) {
|
||||
$playerNamesParam = $chunk -join ','
|
||||
$apiUrl = "https://api.pubg.com/shards/steam/players?filter[playerNames]=$playerNamesParam"
|
||||
Write-Output "Querying for players: $playerNamesParam"
|
||||
|
||||
write-output "Getting data for players $playeridstring gameode $playmode"
|
||||
|
||||
try {
|
||||
$stats = Invoke-RestMethod -Uri "https://api.pubg.com/shards/steam/seasons/lifetime/gameMode/$playmode/players?filter[playerIds]=$playeridstring" -Method GET -Headers $headers
|
||||
}
|
||||
catch {
|
||||
write-output 'sleeping for 61 seconds'
|
||||
start-sleep -Seconds 61
|
||||
$stats = Invoke-RestMethod -Uri "https://api.pubg.com/shards/steam/seasons/lifetime/gameMode/$playmode/players?filter[playerIds]=$playeridstring" -Method GET -Headers $headers
|
||||
}
|
||||
|
||||
|
||||
# Check if the playmode doesn't exist in the hashtable, then add it
|
||||
if (-not $lifetimestats.ContainsKey($playmode)) {
|
||||
$lifetimestats[$playmode] = @{}
|
||||
}
|
||||
$playerInfoResponse = Invoke-PubgApi -Uri $apiUrl -Headers $headers
|
||||
|
||||
if ($null -ne $playerInfoResponse -and $playerInfoResponse.data -is [array]) {
|
||||
$allPlayerInfoData += $playerInfoResponse.data
|
||||
Write-Output "Received data for $($playerInfoResponse.data.Count) players in this chunk."
|
||||
} else {
|
||||
Write-Warning "No valid player data received for chunk: $playerNamesParam"
|
||||
# Consider if script should stop or continue if a chunk fails
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($stat in $stats.data.relationships.player.data.id) {
|
||||
|
||||
# Fetch the player name for the current stat (account ID) from the dictionary
|
||||
$playerName = $playerList | Where-Object { $_.PlayerID -eq $stat } | Select-Object -ExpandProperty PlayerName
|
||||
write-output "Getting data for $playerName with gamemode $playmode"
|
||||
# Fetch the specific stat data for the current stat
|
||||
$specificStat = ($stats.data | where-object { $_.relationships.player.data.id -eq $stat }).attributes.gamemodestats.$playmode
|
||||
# Process player info if data was retrieved
|
||||
$playerList = @()
|
||||
if ($allPlayerInfoData.Count -gt 0) {
|
||||
# Save raw player data
|
||||
$playerDataJsonPath = Join-Path -Path $dataPath -ChildPath "player_data.json"
|
||||
try {
|
||||
@{ data = $allPlayerInfoData } | ConvertTo-Json -Depth 100 | Out-File -FilePath $playerDataJsonPath -Encoding UTF8 -ErrorAction Stop
|
||||
Write-Output "Player data saved to '$playerDataJsonPath'"
|
||||
} catch {
|
||||
Write-Warning "Failed to save player data to '$playerDataJsonPath'. Error: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
# Create a new hashtable entry for the player and insert the specific stat data
|
||||
if (-not $lifetimestats[$playmode].ContainsKey($playerName)) {
|
||||
$lifetimestats[$playmode][$playerName] = @{}
|
||||
# Create simplified player list (Name/ID mapping)
|
||||
$playerList = $allPlayerInfoData | ForEach-Object {
|
||||
if ($_.attributes -ne $null -and $_.id -ne $null) {
|
||||
[PSCustomObject]@{
|
||||
PlayerName = $_.attributes.name
|
||||
PlayerID = $_.id
|
||||
}
|
||||
$lifetimestats[$playmode][$playerName][$stat] = $specificStat
|
||||
} else {
|
||||
Write-Warning "Skipping player entry due to missing attributes or ID: $($_.PSObject.Properties | Out-String)"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Output "Processed $($playerList.Count) players into PlayerList."
|
||||
# $playerList | Format-Table # Optional: Display the list
|
||||
} else {
|
||||
Write-Error "No player information retrieved from API. Cannot proceed with stats fetching."
|
||||
Remove-Lock
|
||||
Stop-Transcript
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Get current date and time
|
||||
# --- Get Lifetime Stats ---
|
||||
Write-Output "Fetching lifetime stats..."
|
||||
$playerModes = @("solo", "duo", "squad", "solo-fpp", "duo-fpp", "squad-fpp")
|
||||
$lifetimeStats = @{} # Use hashtable for structured storage: $lifetimeStats[mode][playerName][accountId] = stats
|
||||
|
||||
# Chunk player IDs for API query (max 10 per request)
|
||||
$playerIdChunks = @()
|
||||
for ($i = 0; $i -lt $playerList.Count; $i += $chunkSize) {
|
||||
$endIndex = [System.Math]::Min($i + $chunkSize - 1, $playerList.Count - 1)
|
||||
$playerIdChunks += ,($playerList[$i..$endIndex].PlayerID)
|
||||
}
|
||||
Write-Output "Split player IDs into $($playerIdChunks.Count) chunks."
|
||||
|
||||
foreach ($idChunk in $playerIdChunks) {
|
||||
$playerIdsParam = $idChunk -join ','
|
||||
foreach ($playMode in $playerModes) {
|
||||
Write-Output "Getting lifetime stats for mode '$playMode', players: $playerIdsParam"
|
||||
$apiUrl = "https://api.pubg.com/shards/steam/seasons/lifetime/gameMode/$playMode/players?filter[playerIds]=$playerIdsParam"
|
||||
|
||||
$statsResponse = Invoke-PubgApi -Uri $apiUrl -Headers $headers
|
||||
|
||||
if ($null -ne $statsResponse -and $statsResponse.data -is [array]) {
|
||||
Write-Verbose "Received $($statsResponse.data.Count) stat entries for mode '$playMode'."
|
||||
# Initialize mode in hashtable if it doesn't exist
|
||||
if (-not $lifetimeStats.ContainsKey($playMode)) {
|
||||
$lifetimeStats[$playMode] = @{}
|
||||
}
|
||||
|
||||
# Process each stat entry in the response
|
||||
foreach ($statEntry in $statsResponse.data) {
|
||||
# Validate structure before accessing nested properties
|
||||
if ($null -ne $statEntry.relationships.player.data.id -and $null -ne $statEntry.attributes.gameModeStats.$playMode) {
|
||||
$accountId = $statEntry.relationships.player.data.id
|
||||
$specificStat = $statEntry.attributes.gameModeStats.$playMode
|
||||
|
||||
# Find player name from our $playerList
|
||||
$playerName = ($playerList | Where-Object { $_.PlayerID -eq $accountId } | Select-Object -First 1).PlayerName
|
||||
|
||||
if ($playerName) {
|
||||
# Initialize player in hashtable if it doesn't exist
|
||||
if (-not $lifetimeStats[$playMode].ContainsKey($playerName)) {
|
||||
$lifetimeStats[$playMode][$playerName] = @{}
|
||||
}
|
||||
# Store the stats under the account ID for that player/mode
|
||||
$lifetimeStats[$playMode][$playerName][$accountId] = $specificStat
|
||||
Write-Verbose "Stored stats for $playerName ($accountId) in mode $playMode."
|
||||
} else {
|
||||
Write-Warning "Could not find player name for account ID '$accountId' in PlayerList."
|
||||
}
|
||||
} else {
|
||||
Write-Warning "Skipping stat entry due to missing data/relationships/gameModeStats: $($statEntry | Out-String)"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Write-Warning "No valid lifetime stats data received for mode '$playMode', players: $playerIdsParam"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Add update timestamp and save lifetime stats
|
||||
$currentDateTime = Get-Date
|
||||
|
||||
# Get current timezone
|
||||
$currentTimezone = (Get-TimeZone).Id
|
||||
|
||||
# Format and parse the information into a string
|
||||
$formattedString = "$currentDateTime - Time Zone: $currentTimezone"
|
||||
$lifetimestats['updated'] = $formattedString
|
||||
# Output the formatted string
|
||||
|
||||
|
||||
$lifetimestats | convertto-json -Depth 100 | out-file "$scriptroot/../data/player_lifetime_data.json"
|
||||
|
||||
|
||||
$seasons = Invoke-RestMethod -Uri "https://api.pubg.com/shards/steam/seasons" -Method GET -Headers $headers
|
||||
$current_season = $seasons.data | Where-Object {$_.attributes.isCurrentSeason -eq $true}
|
||||
|
||||
$i = 0
|
||||
$seasonstats = @()
|
||||
while($playerinfo.data.Count -gt $i) {
|
||||
write-host $clanMembersArray[$i]
|
||||
|
||||
try{
|
||||
$rankedstat = Invoke-RestMethod -Uri "https://api.pubg.com/shards/steam/players/$($playerinfo.data[$i].id)/seasons/$($current_season.id)/ranked" -Method GET -Headers $headers
|
||||
}catch{
|
||||
write-output 'sleeping for 61 seconds'
|
||||
start-sleep -Seconds 61
|
||||
$rankedstat = Invoke-RestMethod -Uri "https://api.pubg.com/shards/steam/players/$($playerinfo.data[$i].id)/seasons/$($current_season.id)/ranked" -Method GET -Headers $headers
|
||||
}
|
||||
|
||||
$seasonstats += [PSCustomObject]@{
|
||||
stat = $rankedstat
|
||||
name = $playerinfo.data[$i].attributes.name
|
||||
}
|
||||
|
||||
$i++
|
||||
$lifetimeStats['updated'] = $formattedString
|
||||
Write-Output "Added update timestamp: $formattedString"
|
||||
|
||||
$lifetimeStatsJsonPath = Join-Path -Path $dataPath -ChildPath "player_lifetime_data.json"
|
||||
try {
|
||||
$lifetimeStats | ConvertTo-Json -Depth 100 | Out-File -FilePath $lifetimeStatsJsonPath -Encoding UTF8 -ErrorAction Stop
|
||||
Write-Output "Lifetime stats saved to '$lifetimeStatsJsonPath'"
|
||||
} catch {
|
||||
Write-Warning "Failed to save lifetime stats to '$lifetimeStatsJsonPath'. Error: $($_.Exception.Message)"
|
||||
}
|
||||
$seasonstats | Sort-Object -Property {$_.stat.data.attributes.rankedGameModeStats.'squad-fpp'.currentRankPoint} -Descending | convertto-json -Depth 100| Out-File "$scriptroot/../data/player_season_data.json"
|
||||
|
||||
remove-lock
|
||||
# --- Get Current Season Ranked Stats ---
|
||||
Write-Output "Fetching current season information..."
|
||||
$currentSeason = $null
|
||||
$seasonsResponse = Invoke-PubgApi -Uri "https://api.pubg.com/shards/steam/seasons" -Headers $headers
|
||||
|
||||
if ($null -ne $seasonsResponse -and $seasonsResponse.data -is [array]) {
|
||||
$currentSeason = $seasonsResponse.data | Where-Object { $_.attributes.isCurrentSeason -eq $true } | Select-Object -First 1
|
||||
}
|
||||
|
||||
if (-not $currentSeason) {
|
||||
Write-Warning "Could not determine the current season from API. Skipping ranked stats update."
|
||||
} else {
|
||||
Write-Output "Current season identified: $($currentSeason.id)"
|
||||
$seasonStats = @()
|
||||
|
||||
# Iterate through the validated $playerList
|
||||
foreach ($player in $playerList) {
|
||||
Write-Output "Getting ranked stats for player: $($player.PlayerName) ($($player.PlayerID))"
|
||||
$apiUrl = "https://api.pubg.com/shards/steam/players/$($player.PlayerID)/seasons/$($currentSeason.id)/ranked"
|
||||
|
||||
$rankedStatResponse = Invoke-PubgApi -Uri $apiUrl -Headers $headers
|
||||
|
||||
# Even if API call returns null (e.g., player has no ranked stats), store an entry
|
||||
$seasonStats += [PSCustomObject]@{
|
||||
stat = $rankedStatResponse # Store the whole response (or null)
|
||||
name = $player.PlayerName
|
||||
}
|
||||
Write-Verbose "Stored ranked stat entry for $($player.PlayerName)."
|
||||
}
|
||||
|
||||
# Save season stats (sorting might fail if 'stat' or nested properties are null)
|
||||
$seasonStatsJsonPath = Join-Path -Path $dataPath -ChildPath "player_season_data.json"
|
||||
try {
|
||||
# Sort carefully, handling potential nulls
|
||||
$sortedSeasonStats = $seasonStats | Sort-Object -Property { if ($null -ne $_.stat.data.attributes.rankedGameModeStats.'squad-fpp'.currentRankPoint) { $_.stat.data.attributes.rankedGameModeStats.'squad-fpp'.currentRankPoint } else { 0 } } -Descending
|
||||
|
||||
$sortedSeasonStats | ConvertTo-Json -Depth 100 | Out-File -FilePath $seasonStatsJsonPath -Encoding UTF8 -ErrorAction Stop
|
||||
Write-Output "Season stats saved to '$seasonStatsJsonPath'"
|
||||
} catch {
|
||||
Write-Warning "Failed to save season stats to '$seasonStatsJsonPath'. Sorting or file write failed. Error: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
# --- Cleanup ---
|
||||
Write-Output "Script finished at $(Get-Date)."
|
||||
Remove-Lock
|
||||
Stop-Transcript
|
||||
Loading…
Add table
Add a link
Reference in a new issue