Private/ConvertFrom-StoreHtml.ps1

function ConvertFrom-StoreHtml {
    <#
    .SYNOPSIS
        Parses the HTML table returned by the store.rg-adguard.net API into structured objects.
    .PARAMETER Html
        Raw HTML string from Invoke-StoreAPI.
    .PARAMETER Channel
        The ring that was queried, attached to every output object.
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [string]$Html,

        [Parameter(Mandatory)]
        [string]$Channel
    )

    process {
        # Extract every <tr>...</tr> block.
        $rowPattern = '(?is)<tr[^>]*>(.*?)</tr>'
        $rowMatches = [regex]::Matches($Html, $rowPattern)

        if ($rowMatches.Count -eq 0) {
            Write-Warning "ConvertFrom-StoreHtml: No table rows found in response for channel $Channel."
            return
        }

        # Pattern for a data row. Header rows won't have an <a> with href.
        $cellPattern = '(?is)<td[^>]*>\s*<a\s+href="(?<url>[^"]+)"[^>]*>(?<name>[^<]+)</a>\s*</td>' +
        '\s*<td[^>]*>(?<expire>[^<]*)</td>' +
        '\s*<td[^>]*>(?<sha1>[^<]*)</td>' +
        '\s*<td[^>]*>(?<size>[^<]*)</td>'

        $parsed = 0

        foreach ($row in $rowMatches) {
            $inner = $row.Groups[1].Value

            if ($inner -match $cellPattern) {
                $parsed++

                $name = [System.Net.WebUtility]::HtmlDecode($Matches['name']).Trim()
                $url = [System.Net.WebUtility]::HtmlDecode($Matches['url']).Trim()
                $expire = [System.Net.WebUtility]::HtmlDecode($Matches['expire']).Trim()
                $sha1 = [System.Net.WebUtility]::HtmlDecode($Matches['sha1']).Trim()
                $size = [System.Net.WebUtility]::HtmlDecode($Matches['size']).Trim()

                # Skip rows with invalid download URLs.
                if ([string]::IsNullOrWhiteSpace($url) -or $url -notmatch '^https?://') {
                    Write-Verbose "Skipping row with invalid URL: $url"
                    continue
                }

                [PSCustomObject]@{
                    PSTypeName = 'WinStoreRip.PackageInfo'
                    Name       = $name
                    URL        = $url
                    ExpireTime = $expire
                    SHA1       = $sha1
                    Size       = $size
                    Channel    = $Channel
                }
            }
        }

        if ($parsed -eq 0) {
            Write-Warning "ConvertFrom-StoreHtml: Table rows found but none matched expected structure. The API format may have changed."
        } else {
            Write-Verbose "Parsed $parsed package(s) from $Channel channel."
        }
    }
}