Public/Invoke-BluestacksEventAnalysis.ps1
|
function Invoke-BluestacksEventAnalysis { <# .SYNOPSIS Analyses a raw BlueStacks macro JSON file and returns structured statistics. .DESCRIPTION Reads a BlueStacks InputMapper JSON file and returns a PSCustomObject containing: TypeCounts - ordered array of EventType / Count pairs ClickBuckets - top coordinate-grouped click pairs with hold-time stats Swipes - detected drag gestures with start/end coordinates Human-readable output is written to the Verbose stream so the function remains fully testable without stream redirection. .PARAMETER Path Path to the BlueStacks macro JSON file to analyse. .OUTPUTS PSCustomObject with properties: Path, TotalEvents, TypeCounts, ClickBuckets, Swipes. .EXAMPLE Invoke-BluestacksEventAnalysis -Path '.\wave-14.json' | Select-Object -ExpandProperty ClickBuckets .EXAMPLE Invoke-BluestacksEventAnalysis -Path '.\wave-14.json' -Verbose #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory)] [string] $Path ) if (-not (Test-Path $Path)) { throw "Path not found: $Path" } $events = @((Get-Content $Path -Raw | ConvertFrom-Json).Events) if ($events.Count -eq 0) { Write-Verbose "No events found in '$Path'." return [PSCustomObject]@{ Path = $Path TotalEvents = 0 TypeCounts = @() ClickBuckets = @() Swipes = @() } } Write-Verbose "=== Event Analysis: $Path ===" Write-Verbose "Total events: $($events.Count)" # -- EventType distribution ------------------------------------------------------- $typeCounts = @( $events | Group-Object -Property EventType | Sort-Object -Property Count -Descending | ForEach-Object { [PSCustomObject]@{ EventType = $_.Name; Count = $_.Count } } ) Write-Verbose 'EventType distribution:' foreach ($tc in $typeCounts) { Write-Verbose (' {0,-10} {1,6}' -f $tc.EventType, $tc.Count) } # -- Click pairs ------------------------------------------------------------------- $clickPairs = [System.Collections.Generic.List[PSCustomObject]]::new() for ($i = 0; $i -lt ($events.Count - 1); $i++) { $cur = $events[$i] $nxt = $events[$i + 1] if ($cur.EventType -eq 'MouseDown' -and $nxt.EventType -eq 'MouseUp' -and [double]$cur.X -eq [double]$nxt.X -and [double]$cur.Y -eq [double]$nxt.Y) { $clickPairs.Add([PSCustomObject]@{ X = [double]$cur.X Y = [double]$cur.Y HoldMs = [long]$nxt.Timestamp - [long]$cur.Timestamp StartTimestamp = [long]$cur.Timestamp }) $i++ # skip the MouseUp we just consumed } } $clickBuckets = @( $clickPairs | Group-Object -Property { '{0:0.##},{1:0.##}' -f $_.X, $_.Y } | Sort-Object -Property Count -Descending | ForEach-Object { $holds = $_.Group.HoldMs $avgHold = [math]::Round((($holds | Measure-Object -Average).Average), 1) [PSCustomObject]@{ Coordinate = $_.Name Count = $_.Count AvgHoldMs = $avgHold } } ) Write-Verbose 'Top click buckets (snippet candidates):' foreach ($bucket in $clickBuckets | Select-Object -First 12) { Write-Verbose (' ({0,-12}) count={1,4} avgHoldMs={2,6}' -f $bucket.Coordinate, $bucket.Count, $bucket.AvgHoldMs) } # -- Swipes ----------------------------------------------------------------------- $swipes = [System.Collections.Generic.List[PSCustomObject]]::new() $index = 0 while ($index -lt $events.Count) { $evt = $events[$index] if ($evt.EventType -ne 'MouseDown') { $index++; continue } $startEvt = $evt $cursor = $index + 1 $moveCount = 0 while ($cursor -lt $events.Count) { $nextEvt = $events[$cursor] if ($nextEvt.EventType -eq 'MouseMove') { $moveCount++; $cursor++; continue } if ($nextEvt.EventType -eq 'MouseUp') { if ($moveCount -gt 0) { $swipes.Add([PSCustomObject]@{ StartX = [double]$startEvt.X StartY = [double]$startEvt.Y EndX = [double]$nextEvt.X EndY = [double]$nextEvt.Y MoveCount = $moveCount DurationMs = [long]$nextEvt.Timestamp - [long]$startEvt.Timestamp }) } $index = $cursor + 1 break } break } if ($cursor -ge $events.Count) { $index++ } } Write-Verbose 'Swipe/drag gestures:' foreach ($swipe in $swipes | Select-Object -First 10) { Write-Verbose (' ({0},{1}) -> ({2},{3}) moves={4,3} durationMs={5,5}' -f $swipe.StartX, $swipe.StartY, $swipe.EndX, $swipe.EndY, $swipe.MoveCount, $swipe.DurationMs) } return [PSCustomObject]@{ Path = $Path TotalEvents = $events.Count TypeCounts = $typeCounts ClickBuckets = $clickBuckets Swipes = $swipes.ToArray() } } |