119 lines
3.9 KiB
PowerShell
119 lines
3.9 KiB
PowerShell
# Mohe & XiaoXiaoMo Chat Viewer
|
|
# Lists [mohe]/[xxm] messages from an OpenCode session.
|
|
#
|
|
# Usage:
|
|
# .\moho_chat.ps1 <session_id> [minutes]
|
|
#
|
|
# Examples:
|
|
# .\moho_chat.ps1 ses_1d95d15c4ffehQaZ6hrbIbak5k
|
|
# .\moho_chat.ps1 ses_1d95d15c4ffehQaZ6hrbIbak5k 30
|
|
|
|
param(
|
|
[Parameter(Mandatory=$true)][string]$SessionId,
|
|
[int]$Minutes = 0
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
Write-Host "Exporting session..." -ForegroundColor DarkGray
|
|
|
|
$tmpFile = [System.IO.Path]::GetTempFileName() + ".json"
|
|
$env:CI = 'true'
|
|
opencode.cmd export $SessionId 2>$null | Set-Content -Path $tmpFile -NoNewline -Encoding UTF8
|
|
|
|
if (-not (Test-Path $tmpFile) -or (Get-Item $tmpFile).Length -eq 0) {
|
|
Write-Error "Export failed"
|
|
exit 1
|
|
}
|
|
|
|
$size = (Get-Item $tmpFile).Length
|
|
Write-Host "Read ($($size / 1MB -as [int]) MB)" -ForegroundColor DarkGray
|
|
|
|
$raw = Get-Content $tmpFile -Raw -Encoding UTF8
|
|
Remove-Item $tmpFile -Force
|
|
|
|
# Check if it's actually UTF-16LE in disguise (opencode export uses UTF-16LE)
|
|
if ($raw.Length -eq 0 -or $raw[0] -ne '{') {
|
|
# Re-read as UTF-16
|
|
$bytes = [System.IO.File]::ReadAllBytes($tmpFile)
|
|
Remove-Item $tmpFile -Force
|
|
$raw = [System.Text.Encoding]::Unicode.GetString($bytes)
|
|
}
|
|
|
|
$brace = $raw.IndexOf('{')
|
|
if ($brace -gt 0) { $raw = $raw.Substring($brace) }
|
|
|
|
# Use regex to find "text": "[mohe]..." patterns directly (no JSON parsing)
|
|
$pattern = '"text":\s*"\[(mohe|xxm)\][^"]*"'
|
|
|
|
$cutoff = if ($Minutes -gt 0) { (Get-Date).ToUniversalTime().AddMinutes(-$Minutes) } else { $null }
|
|
|
|
$results = @()
|
|
$matchPos = 0
|
|
|
|
while ($true) {
|
|
$m = [regex]::Match($raw, $pattern, $matchPos)
|
|
if (-not $m.Success) { break }
|
|
$matchPos = $m.Index + 1
|
|
|
|
$tag = $m.Groups[1].Value
|
|
$content = $m.Value
|
|
|
|
# Extract the full text content (everything between "text": " and the closing ")
|
|
$start = $m.Index + $m.Value.IndexOf('"', $m.Value.IndexOf(':')+1) + 1
|
|
$fullText = ''
|
|
$escape = $false
|
|
for ($i = $start; $i -lt $raw.Length; $i++) {
|
|
$c = $raw[$i]
|
|
if ($escape) { $fullText += $c; $escape = $false }
|
|
elseif ($c -eq '\') { $fullText += $c; $escape = $true }
|
|
elseif ($c -eq '"') { break }
|
|
else { $fullText += $c }
|
|
}
|
|
|
|
$fullText = $fullText.Trim()
|
|
if (-not ($fullText.StartsWith('[mohe]') -or $fullText.StartsWith('[xxm]'))) { continue }
|
|
|
|
$sender = if ($fullText.StartsWith('[mohe]')) { '[mohe] Mohe' } else { '[xxm] XiaoXiaoMo' }
|
|
$display = $fullText -replace '^\[\w+\]\s*', ''
|
|
|
|
# Get timestamp by looking backward
|
|
$before = $raw.Substring([Math]::Max(0, $m.Index - 3000), [Math]::Min(3000, $m.Index))
|
|
$tsMatch = [regex]::Match($before, '"timestamp":\s*"([^"]+)"')
|
|
$tsStr = if ($tsMatch.Success) { $tsMatch.Groups[1].Value } else { '' }
|
|
|
|
if (-not $tsStr) {
|
|
$crMatch = [regex]::Match($before, '"created":\s*(\d+)')
|
|
if ($crMatch.Success) {
|
|
$tsStr = ([DateTimeOffset]::FromUnixTimeMilliseconds([long]$crMatch.Groups[1].Value).UtcDateTime).ToString('o')
|
|
}
|
|
}
|
|
|
|
$ts = $null
|
|
if ($tsStr) {
|
|
try { $ts = [DateTime]::Parse($tsStr, $null, [System.Globalization.DateTimeStyles]::AssumeUniversal) } catch {}
|
|
}
|
|
|
|
if ($cutoff -and $ts -and $ts -lt $cutoff) { continue }
|
|
|
|
$timeLocal = if ($ts) { $ts.ToLocalTime().ToString('HH:mm:ss') } else { '??' }
|
|
|
|
$results += [PSCustomObject]@{ Time=$timeLocal; Sort=if($ts){$ts.Ticks}else{0}; Sender=$sender; Message=$display }
|
|
}
|
|
|
|
$results = $results | Sort-Object Sort
|
|
|
|
if ($results.Count -eq 0) {
|
|
Write-Host "No [mohe]/[xxm] messages found" -ForegroundColor Yellow
|
|
if ($Minutes -gt 0) { Write-Host "(last $Minutes minutes)" }
|
|
exit
|
|
}
|
|
|
|
Write-Host "`n$($results.Count) message(s)" -ForegroundColor Cyan
|
|
if ($Minutes -gt 0) { Write-Host "(last $Minutes min)" }
|
|
Write-Host ("=" * 60)
|
|
|
|
foreach ($r in $results) {
|
|
Write-Host ("[{0}] {1}: {2}" -f $r.Time, $r.Sender, $r.Message)
|
|
}
|