429 lines
No EOL
23 KiB
PowerShell
429 lines
No EOL
23 KiB
PowerShell
# --- 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)"
|
|
|
|
# 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"
|
|
|
|
# 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"
|
|
|
|
# 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
|
|
}
|
|
}
|
|
}
|
|
|
|
# --- 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)"
|
|
}
|
|
} 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'."
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
# --- 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 }
|
|
}
|
|
}
|
|
return $null
|
|
}
|
|
|
|
# --- 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
|
|
} |