PSKindleWatch.psm1

$ErrorActionPreference = "Stop"

function Import-KindleDataFile {
    Param (
        [Parameter(Mandatory = $true)]
        [ValidateScript( { Test-Path -Path $_ })]
        [string]$DataFile
    )

    Write-Verbose "Importing Kindle Data file"

    try {
        $BookData = Get-Content -Path $DataFile | ConvertFrom-Json
        Write-Output $BookData
    }
    catch {
        Write-Host "Unable to load or parse data file ($DataFile). The exception message is below." -ForegroundColor Red
        Write-Host $_.Exception.Message -ForegroundColor Red 
    }
}

function Export-KindleDataFile {

    Param(
        [Parameter(Mandatory = $true)]
        [ValidateScript( { Test-Path $_ })]
        [string]$DataFile,

        [Parameter(Mandatory = $true)]
        [psobject]$BookData,

        [Parameter(Mandatory = $false)]
        [string]$Message = $null
    )

    Write-Verbose "Exporting Kindle Data file"

    try {
        $BookData | ConvertTo-Json -Depth 100 | Out-File $DataFile -Force -Encoding utf8
        if ($Message) {
            Write-Host $Message
        }
    }
    catch {
        Write-Host "Unable to write to data file ($DataFile). The exception message is below."  -ForegroundColor Red
        Write-Host $_.Exception.Message -ForegroundColor Red
    }
}

function Get-AmazonData {

    Param (
        [Parameter(Mandatory = $true)]
        [string]$ASIN,
        [Parameter(Mandatory = $true)]
        [uri]$URL
    )

    Write-Verbose "Querying Amazon API"

    $Body = @{
        method = "getBookData"
        asin   = $ASIN
    }

    try {
        $Result = Invoke-RestMethod -Method Post -ContentType "application/x-www-form-urlencoded" -Uri "https://$($URL.Host)/gp/search-inside/service-data" -Body $Body -ErrorAction Stop
    }
    catch {
        Write-Host "API call was not successful. The exception message is below." -ForegroundColor Red
        Write-Host $_.Exception.Message -ForegroundColor Red
    }

    Write-Verbose "---- Start API Lookup Result ---------------------"
    Write-Verbose $Result | Out-String
    Write-Verbose "---- End API Lookup Result -----------------------"

    Write-Output $Result

}

function Add-KindleBook {
    [CmdletBinding()]

    Param (
        [Parameter(Mandatory = $true)]
        [ValidateScript( { $_.HostNameType -eq "DNS" })]
        [System.Uri]$BookURL, 

        [Parameter(Mandatory = $false)]
        [string]$DataFile = [Environment]::GetFolderPath("MyDocuments") + "\KindleBooks.json"
    )

    if (!(Test-Path $DataFile)) {
        New-Item -Path $DataFile -ItemType File | Out-Null
        $BookData = @()
    }
    else {
        $BookData = Import-KindleDataFile -DataFile $DataFile

        # We need this because Write-Output in Import-KindleDataFile converts a single item array
        # into an object (essentially dropping the array). This causes issues when we attempt to
        # add other objects to the array
        if ($BookData -is [PSCustomObject]) {
            [array]$BookData = @($BookData)
        }
    }

    $ASIN = $BookURL.ToString().Split("/")
    $ASINIndex = [array]::IndexOf($ASIN, "dp") + 1
    $ASIN = $ASIN[$ASINIndex]

    if ($ASIN.IndexOf("?") -ne -1) {
        $ASIN = $ASIN.Split("?")[0]
    }

    Write-Verbose "Extracted ASIN: $ASIN"  

    Write-Verbose "Checking if ASIN is already present in the data file"
    if ($ASIN -in $BookData.ASIN) {
        Write-Host "Book with ASIN $ASIN already exists in data file ($DataFile)" -ForegroundColor Gray
    }
    else {

        $Result = Get-AmazonData -ASIN $ASIN -URL $BookURL
        
        $Data = @{
            Title         = $Result.title
            Authors       = [array]$Result.authorNameList
            ASIN          = $Result.ASIN
            URL           = $BookURL
            ImageURL      = $Result.thumbnailImage.Replace("._SL75_", "")
            OriginalPrice = [decimal]$Result.buyingPrice.Replace("$", "")
            IsOnSale      = $false
            SalePrice     = 0
        }

        if ($Data.Title) {
            $obj = New-Object -TypeName PSCustomObject -Property $Data

            Write-Verbose "---- Start Object Dump ---------------------"
            Write-Verbose $obj | Out-String
            Write-Verbose "---- End Object Dump -----------------------"    
    
            $BookData += $obj            
            $Message = "Successfull added $($obj.Title)"
            
            Export-KindleDataFile -BookData $BookData -DataFile $DataFile -Message $Message
        }
        else {
            Write-Error "API call did not return required data. Please ensure the URL is of the Kindle edition of the book."
        }
    }

}

function Remove-KindleBook {
    param (
        [Parameter(Mandatory = $true)]
        [string]$BookTitle, 

        [Parameter(Mandatory = $false)]
        [string]$DataFile = [Environment]::GetFolderPath("MyDocuments") + "\KindleBooks.json"
    )

    $BookData = Import-KindleDataFile -DataFile $DataFile
    $BooksToBeRemoved = ($BookData | ? { $_.Title -match $BookTitle }).Title -join "`n -"
    if ($BooksToBeRemoved) {
        Write-Host "Removing: `n -$BooksToBeRemoved"
        $BookData = $BookData | ? { $_.Title -notmatch $BookTitle }


        if ($BookData -eq $null) {
            Write-Host "Book list is empty!"
            $BookData = @()
        }

        Export-KindleDataFile -DataFile $DataFile -BookData $BookData
    }
    else {
        Write-Host "No matching book titles found"
    }
}


function Get-KindleBooks {
    [CmdletBinding()]

    Param (
        [Parameter(Mandatory = $false)]
        [ValidateScript( { Test-Path -Path $_ })]
        [string]$DataFile = [Environment]::GetFolderPath("MyDocuments") + "\KindleBooks.json"
    )

    [array]$BookData = Import-KindleDataFile -DataFile $DataFile
    $Authors = @{Label="Authors";Expression={$_.Authors -join ", "}}

    Write-Output $BookData | Select Title, $Authors, OriginalPrice
}


function Update-KindleBookPrices {
    [CmdletBinding()]

    Param (
        [Parameter(Mandatory = $false)]
        [scriptblock]$AlertScriptBlock = $null,

        [Parameter(Mandatory = $false)]
        [ValidateScript( { Test-Path -Path $_ })]
        [string]$DataFile = [Environment]::GetFolderPath("MyDocuments") + "\KindleBooks.json"
    )

    $i = 0
    [array]$BookData = Import-KindleDataFile -DataFile $DataFile
    $BookCount = $BookData.count

    if ($?) {

        foreach ($Book in $BookData) {

            Write-Progress -Activity "Checking book prices" -Status "Progress:" -PercentComplete ($i / $BookCount * 100)
            $i++
            
            $Body = @{
                method = "getBookData"
                asin   = $Book.ASIN
            }
    
            Write-Host "Checking price of $($Book.Title)"

            $CurrentResult = Get-AmazonData -ASIN $Book.ASIN -URL $Book.URL
            $CurrentPrice = [decimal]$CurrentResult.buyingPrice.Replace("$", "")
            $Message = $null

            # Condition for original price being discounted
            if (($CurrentPrice -lt $Book.OriginalPrice) -and ($Book.IsOnSale -eq $false)) {
                $Message = "Found discount for $($Book.Title), new price is `$$CurrentPrice (original: `$$($Book.OriginalPrice))"
                Write-Host $Message -ForegroundColor Green
                $Book.SalePrice = $CurrentPrice
                $Book.IsOnSale = $true
            }

            # Condition for further discount being applied to existing discount
            if (($CurrentPrice -lt $Book.SalePrice) -and ($Book.IsOnSale -eq $true)) {
                $Message = "Additional discount found for $($Book.Title), new price is `$$CurrentPrice (original: `$$($Book.OriginalPrice))"
                Write-Host $Message -ForegroundColor Green
                $Book.SalePrice = $CurrentPrice
            }

            # Condition for reduced discount being applied to existing discount
            if (($CurrentPrice -gt $Book.SalePrice) -and ($Book.IsOnSale -eq $true)) {
                $Message = "Discount reduced for $($Book.Title), new price is `$$CurrentPrice (up from previous discount of `$$($Book.SalePrice) and original: $($Book.OriginalPrice))"
                Write-Host $Message -ForegroundColor Yellow
                $Book.SalePrice = $CurrentPrice
            }

            # Condition for sale ending
            if (($CurrentPrice -ge $Book.OriginalPrice) -and ($Book.IsOnSale -eq $true)) {
                $Message = "Sale ended for $($Book.Title), current price is `$$CurrentPrice"
                Write-Host $Message -ForegroundColor Red
                $Book.OriginalPrice = $CurrentPrice
                $Book.SalePrice = 0
                $Book.IsOnSale = $false
            }

            # Condition for price increase
            if (($CurrentPrice -gt $Book.OriginalPrice) -and ($Book.IsOnSale -eq $false)) {
                $Message = "Price increased for $($Book.Title), new price is `$$CurrentPrice (original: `$$($Book.OriginalPrice))"
                Write-Host $Message -ForegroundColor Red
                $Book.OriginalPrice = $CurrentPrice
            }

            if ($AlertScriptBlock -and $Message) {
                Write-Verbose "Triggering configured alert"
                Invoke-Command -ScriptBlock $AlertScriptBlock -ArgumentList $Book, $Message
            }

            Start-Sleep -Seconds 1
        }
    }

    Export-KindleDataFile -BookData $BookData -DataFile $DataFile
    Write-Progress -Completed -Activity "Checking book prices"
}