内部/工具函数.ps1

# 工具函数
# API 字段名(mime_type/inline_data 等)保持英文

function Get-Mime类型 {
    param([string]$路径)
    switch ([System.IO.Path]::GetExtension($路径).ToLowerInvariant()) {
        '.png'  { 'image/png' }
        '.jpg'  { 'image/jpeg' }
        '.jpeg' { 'image/jpeg' }
        '.gif'  { 'image/gif' }
        '.webp' { 'image/webp' }
        default { 'application/octet-stream' }
    }
}

function Get-图像数据 {
    <#
    .SYNOPSIS
        从本地路径或 URL 获取图像字节和文件名。
    #>

    param([string]$来源)
    if ($来源 -match '^https?://') {
        try { $响应 = Invoke-WebRequest -Uri $来源 -TimeoutSec 60 -ErrorAction Stop }
        catch { throw "下载图像失败($来源):$($_.Exception.Message)" }
        $文件名 = [System.IO.Path]::GetFileName($来源.Split('?')[0])
        if (-not $文件名 -or $文件名 -eq '') { $文件名 = 'image.png' }
        # 强制包成数组,避免单字节时管道展平为标量
        return @(, @{ 字节 = ([byte[]]$响应.Content); 文件名 = $文件名 })
    }
    $解析后 = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($来源)
    if (-not (Test-Path -LiteralPath $解析后 -PathType Leaf)) {
        throw "参考图不存在:$来源"
    }
    return @{ 字节 = [System.IO.File]::ReadAllBytes($解析后); 文件名 = [System.IO.Path]::GetFileName($解析后) }
}

function Read-提示文件 {
    param([string]$路径)
    if (-not (Test-Path -LiteralPath $路径 -PathType Leaf)) {
        throw "提示文件不存在:$路径"
    }
    $内容 = [System.IO.File]::ReadAllText(
        $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($路径),
        [System.Text.Encoding]::UTF8).Trim()
    if ([string]::IsNullOrWhiteSpace($内容)) {
        throw "提示文件内容为空:$路径"
    }
    return $内容
}

function Resolve-输出路径 {
    param([string]$输出路径)
    $解析后 = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($输出路径)
    $目录 = Split-Path -Parent $解析后
    if ($目录 -and -not (Test-Path $目录)) {
        New-Item -ItemType Directory -Path $目录 -Force | Out-Null
    }
    return $解析后
}

function Get-NetException详情 {
    <#
    .SYNOPSIS
        递归展开 .NET 异常链,提取真实的 HTTP 错误内容。
    .DESCRIPTION
        PowerShell 5.1 调用 .NET 方法抛出的异常会被包装成 MethodInvocationException,
        HttpClient 在服务端报错且关闭连接时常抛 TaskCanceledException("A task was canceled"),
        而真实的服务器错误(状态码 + 响应体 JSON)位于内部 WebException 的 Response 流中。
        本函数沿 InnerException 链逐层展开,找到带 Response 的 WebException 并读取其内容;
        找不到时拼接整条异常链的消息,避免只报 "A task was canceled" 而无细节。
    .PARAMETER Exception
        最外层异常对象(通常是 MethodInvocationException 或 TaskCanceledException)。
    #>

    param([Exception]$Exception)

    # 逐层展开,优先找带 HTTP 响应的 WebException
    $当前 = $Exception
    $深度 = 0
    while ($当前 -and $深度 -lt 8) {
        if ($当前 -is [System.Net.WebException] -and $当前.Response) {
            $响应对象 = $当前.Response
            $响应体 = ''
            try {
                $流 = $响应对象.GetResponseStream()
                if ($流) {
                    $流.Position = 0
                    $响应体 = [System.IO.StreamReader]::new($流).ReadToEnd()
                }
            }
            catch { }
            if ([string]::IsNullOrWhiteSpace($响应体)) { $响应体 = $当前.Message }
            $状态码 = ''
            $状态码属性 = $响应对象.PSObject.Properties['StatusCode']
            if ($状态码属性) { $状态码 = "HTTP $([int]$状态码属性.Value): " }
            return "$状态码$响应体"
        }
        $深度++
        $当前 = $当前.InnerException
    }

    # 未找到 WebException:拼接整条异常链的消息
    $消息列表 = [System.Collections.Generic.List[string]]::new()
    $当前 = $Exception
    $深度 = 0
    while ($当前 -and $深度 -lt 8) {
        $消息列表.Add($当前.Message)
        $深度++
        $当前 = $当前.InnerException
    }
    return ($消息列表 -join ' -> ')
}