MappingEncrypt.psm1

#Requires -Version 5.1
<#
    MappingEncrypt — PowerShell port of the "mapping-encrypt" Python package.

    Identical behavior: encodes/decodes text through the SAME private Cloudflare
    mapping worker (same URL, same API key, same /encode and /decode endpoints,
    same "|" delimiter). Encoding a script produces a self-decoding stub that
    fetches the decode from Cloudflare at runtime and runs it via Invoke-Expression
    (the PowerShell equivalent of Python's exec()).

    Python -> PowerShell
      mapping.encode(text) -> ConvertTo-MappingEncoded
      mapping.decode(encoded) -> ConvertFrom-MappingEncoded
      mapping-encode (CLI) -> Protect-MappingScript (alias: mapping-encode)
      mapping-decode (CLI) -> Unprotect-MappingScript (alias: mapping-decode)
      mapping-pkg (CLI) -> Invoke-MappingPackage
#>


$script:WorkerUrl = 'https://mapping-worker.email-ecf.workers.dev'
$script:ApiKey    = 'b19b8317f4c0239b7df33d5199ba67dfd40291da28d56f3697e8f455d9953774'


function ConvertTo-MappingEncoded {
    <#
    .SYNOPSIS
        Encode text into mapping tokens via the Cloudflare worker. (= Python mapping.encode)
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory, Position = 0, ValueFromPipeline)]
        [string] $Text,

        [string] $Delimiter = '|'
    )
    process {
        $payload = @{ text = $Text; delimiter = $Delimiter } | ConvertTo-Json -Compress -Depth 5
        $bytes   = [System.Text.Encoding]::UTF8.GetBytes($payload)
        try {
            $resp = Invoke-RestMethod -Uri "$script:WorkerUrl/encode" -Method Post -Body $bytes `
                -Headers @{ 'X-API-Key' = $script:ApiKey } -ContentType 'application/json; charset=utf-8'
        }
        catch {
            $detail = $_.ErrorDetails.Message
            if ($detail) {
                try { $detail = ($detail | ConvertFrom-Json).error } catch { }
                throw "Encoding failed: $detail"
            }
            throw
        }
        return $resp.encoded
    }
}


function ConvertFrom-MappingEncoded {
    <#
    .SYNOPSIS
        Decode mapping tokens back into text via the Cloudflare worker. (= Python mapping.decode)
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory, Position = 0, ValueFromPipeline)]
        [string] $EncodedString,

        [string] $Delimiter = '|',

        [switch] $Strict
    )
    process {
        $payload = @{ encoded = $EncodedString; delimiter = $Delimiter; strict = [bool]$Strict } |
            ConvertTo-Json -Compress -Depth 5
        $bytes = [System.Text.Encoding]::UTF8.GetBytes($payload)
        $resp  = Invoke-RestMethod -Uri "$script:WorkerUrl/decode" -Method Post -Body $bytes `
            -Headers @{ 'X-API-Key' = $script:ApiKey } -ContentType 'application/json; charset=utf-8'
        return $resp.decoded
    }
}


function Protect-MappingScript {
    <#
    .SYNOPSIS
        Encode a .ps1 file into a self-decoding stub. (= Python mapping-encode CLI)
    #>

    [CmdletBinding()]
    [Alias('mapping-encode')]
    param(
        [Parameter(Mandatory, Position = 0)]
        [string] $Path,

        [Parameter(Position = 1)]
        [Alias('o', 'OutputPath')]
        [string] $OutputFile
    )

    if (-not (Test-Path -LiteralPath $Path)) { throw "Input file not found: $Path" }
    $text = Get-Content -LiteralPath $Path -Raw -Encoding UTF8

    $encoded = ConvertTo-MappingEncoded -Text $text

    if (-not $OutputFile) {
        $dir  = Split-Path -Parent $Path
        $base = [System.IO.Path]::GetFileNameWithoutExtension($Path)
        $ext  = [System.IO.Path]::GetExtension($Path)
        if (-not $ext) { $ext = '.ps1' }
        $name = "${base}_encoded${ext}"
        $OutputFile = if ($dir) { Join-Path $dir $name } else { $name }
    }

    # Single-quoted so tokens containing '$' (e.g. the '"' token c$HCi) are NOT
    # treated as PowerShell variables. Tokens never contain a single quote.
    $stub = @"
Import-Module MappingEncrypt

`$encodedString = '$encoded'

`$decodedString = ConvertFrom-MappingEncoded -EncodedString `$encodedString

Invoke-Expression `$decodedString
"@


    Set-Content -LiteralPath $OutputFile -Value $stub -Encoding UTF8
    Write-Host "Encoded script saved to $OutputFile"
    return $OutputFile
}


function Unprotect-MappingScript {
    <#
    .SYNOPSIS
        Decode an encrypted .ps1 file back to raw code. (= Python mapping-decode CLI)
    #>

    [CmdletBinding()]
    [Alias('mapping-decode')]
    param(
        [Parameter(Mandatory, Position = 0)]
        [string] $Path,

        [Parameter(Position = 1)]
        [Alias('o', 'OutputPath')]
        [string] $OutputFile
    )

    if (-not (Test-Path -LiteralPath $Path)) { throw "Input file not found: $Path" }
    $content = Get-Content -LiteralPath $Path -Raw -Encoding UTF8

    $match = [regex]::Match($content, "\`$encodedString\s*=\s*['`"]([^'`"]+)['`"]")
    if (-not $match.Success) {
        throw "No encodedString found in file - is this an encrypted script?"
    }
    $encoded = $match.Groups[1].Value

    $raw = ConvertFrom-MappingEncoded -EncodedString $encoded

    if (-not $OutputFile) {
        $base = $Path -replace '_encoded\.ps1$', '.ps1' -replace '_encrypted\.ps1$', '.ps1'
        if ($base -ne $Path) { $OutputFile = $base }
        else { $OutputFile = $Path -replace '\.ps1$', '_decoded.ps1' }
    }

    Set-Content -LiteralPath $OutputFile -Value $raw -Encoding UTF8 -NoNewline
    Write-Host "Decoded script saved to $OutputFile"
    return $OutputFile
}


function Invoke-MappingPackage {
    <#
    .SYNOPSIS
        Install / uninstall / reinstall the module from PowerShell Gallery. (= Python mapping-pkg CLI)
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory, Position = 0)]
        [ValidateSet('install', 'uninstall', 'reinstall')]
        [string] $Action
    )

    $module = 'MappingEncrypt'
    switch ($Action) {
        'install' {
            Install-Module $module -Scope CurrentUser -Force
            Write-Host 'Done.'
        }
        'uninstall' {
            Uninstall-Module $module -AllVersions -Force
            Write-Host 'Done.'
        }
        'reinstall' {
            Uninstall-Module $module -AllVersions -Force
            Install-Module $module -Scope CurrentUser -Force
            Write-Host 'Done.'
        }
    }
}


Export-ModuleMember -Function ConvertTo-MappingEncoded, ConvertFrom-MappingEncoded,
    Protect-MappingScript, Unprotect-MappingScript, Invoke-MappingPackage `
    -Alias 'mapping-encode', 'mapping-decode'