PSWeekly.psm1

#Region '.\Classes\link_library_links.ps1' -1

Add-Type -AssemblyName System.Web
enum WeeklyCategory {
    Announcements = 58
    BlogsArticlesAndPosts = 12
    BooksMediaAndLearningResources = 4
    Community = 1143
    Fun = 7
    ProjectsScriptsAndModules = 9
    UpcomingEvents = 17
}

class WeeklyCategoryHelper {
    static [hashtable] $DisplayNames = @{
        [WeeklyCategory]::Announcements = "Announcements!"
        [WeeklyCategory]::BlogsArticlesAndPosts = "Blogs, Articles, and Posts"
        [WeeklyCategory]::BooksMediaAndLearningResources = "Books, Media, and Learning Resources"
        [WeeklyCategory]::Community = "Community"
        [WeeklyCategory]::Fun = "Fun"
        [WeeklyCategory]::ProjectsScriptsAndModules = "Projects, Scripts, and Modules"
        [WeeklyCategory]::UpcomingEvents = "Upcoming Events"
    }

    static [hashtable] $DisplayOrder = @{
        [WeeklyCategory]::Announcements = 0
        [WeeklyCategory]::BlogsArticlesAndPosts = 1
        [WeeklyCategory]::BooksMediaAndLearningResources = 2
        [WeeklyCategory]::Community = 3
        [WeeklyCategory]::Fun = 4
        [WeeklyCategory]::ProjectsScriptsAndModules = 5
        [WeeklyCategory]::UpcomingEvents = 6
    }
    
    static [string] GetDisplayName([WeeklyCategory]$category) {
        return [WeeklyCategoryHelper]::DisplayNames[$category]
    }

    static [int] GetDisplayOrder([WeeklyCategory]$category) {
        return [WeeklyCategoryHelper]::DisplayOrder[$category]
    }
}

class link_library_links {
    [String]$Title
    [String]$Link
    [DateTime]$Date
    [String]$Author
    [String]$Description
    [WeeklyCategory]$WeeklyCategory
    [Object[]]$Category
    [Object[]]$Tags
    
    # Property to get the display name of the WeeklyCategory
    [string] GetWeeklyCategoryDisplayName() {
        return [WeeklyCategoryHelper]::GetDisplayName($this.WeeklyCategory)
    }

    link_library_links([object]$InputObject) {
        $this.Title = [System.Web.HttpUtility]::HtmlDecode($InputObject.title.rendered)
        $this.Link = $InputObject.link
        $this.Date = $InputObject.date
        $this.Author = $InputObject.meta.link_submitter_name
        
        $this.Category = $InputObject.link_library_category
        $this.Tags = $InputObject.link_library_tags

        if(-not [string]::IsNullOrEmpty($InputObject.meta.link_description)) {
            $DescriptionStr = $InputObject.meta.link_description.Replace('[expand title=(+) trigclass=my_button trigpos=below swaptitle=(-) targpos=inline] ','').Replace(' [/expand]','')
            $this.Description = [System.Web.HttpUtility]::HtmlDecode($DescriptionStr)
        }
        
        # Find the category ID that matches a WeeklyCategory enum value
        $enumValues = [WeeklyCategory].GetEnumValues()
        $matchingCategoryId = $InputObject.link_library_category | Where-Object { $_ -in $enumValues }
        
        if ($matchingCategoryId) {
            $this.WeeklyCategory = [WeeklyCategory]$matchingCategoryId
        } else {
            $this.WeeklyCategory = [WeeklyCategory]::BlogsArticlesAndPosts # Default fallback
        }
    }
    
}
#EndRegion '.\Classes\link_library_links.ps1' 83
#Region '.\Private\Get-PSWeeklyLinks.ps1' -1

<#
.SYNOPSIS
Short description
 
.DESCRIPTION
Long description
 
.PARAMETER Tags
Comma-separated list of tag IDs to filter the links.
 
.PARAMETER Category
Comma-separated list of category IDs to filter the links.
 
.PARAMETER Search
Search term to filter the links.
 
.PARAMETER MaxResults
Maximum number of results to return. 0 means no limit.
 
.EXAMPLE
Get-PSWeeklyLinks -Tags 1,2 -Category 3 -Search "Azure" -MaxResults 10
 
.NOTES
General notes
#>



function Get-PSWeeklyLinks {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [int[]]$Tags = @(),
        [Parameter(Mandatory = $false)]
        [int[]]$Category = @(),
        [Parameter(Mandatory = $false)]
        [string]$Search = $null,
        [Parameter(Mandatory = $false)]
        [int]$MaxResults = 0  # 0 means no limit, return all results
    )
    
    $BaseUri = "link_library_links"
    
    $parameters = @()
    if($Tags.Count -gt 0) {
        $parameters += "link_library_tags=" + ($Tags -join ',')
    }
    if($Category.Count -gt 0) {
        $parameters += "link_library_category=" + ($Category -join ',')
    }
    elseif(-not [string]::IsNullOrEmpty($Search)) {
        $parameters += "search=$Search&orderby=date&order=desc"
    }
    if ($parameters.Count -gt 0) {
        $BaseUri += "?$($parameters -join '&')"
    }
    Write-Verbose "Fetching links from: $BaseUri"
    $allLinks = Invoke-PSWeeklyAPI -Path $BaseUri -PerPage 10 -MaxResults $MaxResults

    $return = $allLinks | ForEach-Object { 
        [link_library_links]::new($_)
    }
    $return | Sort-Object WeeklyCategory
}
#EndRegion '.\Private\Get-PSWeeklyLinks.ps1' 64
#Region '.\Private\Get-PSWeeklyPost.ps1' -1

<#
.SYNOPSIS
Retrieves posts from the PSWeekly API.
 
.DESCRIPTION
This function interacts with the PSWeekly API to retrieve posts.
 
.PARAMETER PostId
The ID of the post to retrieve.
 
.PARAMETER Search
The search term to filter posts.
 
.PARAMETER Last
The number of posts to retrieve.
 
.EXAMPLE
Get-PSWeeklyPost -PostId 123
 
.NOTES
General notes
#>

function Get-PSWeeklyPost {
    [CmdletBinding(DefaultParameterSetName = 'Search')]
    param (
        [Parameter(Mandatory = $false, ParameterSetName = 'PostId')]
        [string]$PostId = $null,
        [Parameter(Mandatory = $false, ParameterSetName = 'Search')]
        [string]$Search = $null,
        [Parameter(Mandatory = $false, ParameterSetName = 'Search')]
        [int]$Last = 1
    )
    if ($PSCmdlet.ParameterSetName -eq 'PostId') {
        $posts = Invoke-PSWeeklyAPI -Path "posts/$PostId"
    }
    elseif (-not [string]::IsNullOrEmpty($Search)) {
        $feed = Invoke-PSWeeklyAPI -Path "posts?search=$Search&orderby=date&order=desc" -MaxResults ($Last * 2) # multiple by 2 to ensure we have enough posts to filter down the mobiles to the last N
        $posts = $feed | Where-Object { $_.slug -notmatch 'mobile' } | Select-Object -First $Last
    }
    else {
        $feed = Invoke-PSWeeklyAPI -Path "posts?orderby=date&order=desc" -MaxResults ($Last * 2) # multiple by 2 to ensure we have enough posts to filter down the mobiles to the last N
        $posts = $feed | Where-Object { $_.slug -notmatch 'mobile' } | Select-Object -First $Last
    }
    
    $posts | Select-Object -Property id, date, @{l = 'title'; e = { $_.title.rendered } }, @{l = 'linktag'; e = { [Regex]::Match($_.content_raw, '(?<=taglistoverride\=\")(.*?)(?=\")').Value } }
}
#EndRegion '.\Private\Get-PSWeeklyPost.ps1' 47
#Region '.\Private\Get-PSWeeklyTag.ps1' -1

<#
.SYNOPSIS
Retrieves the tags for PSWeekly links.
 
.DESCRIPTION
This function fetches the tags available for PSWeekly links.
 
.PARAMETER Search
The search term to filter tags.
 
.PARAMETER Last
The number of tags to retrieve.
 
.EXAMPLE
Get-PSWeeklyTag -Search "PowerShell" -Last 5
 
.NOTES
General notes
#>

function Get-PSWeeklyTag {
    [CmdletBinding(DefaultParameterSetName = 'Search')]
    param (
        [Parameter(Mandatory = $false, ParameterSetName = 'Search')]
        [string]$Search = $null,
        [Parameter(Mandatory = $false, ParameterSetName = 'Search')]
        [int]$Last = 1
    )

    $Tags= Invoke-PSWeeklyAPI -Path "link_library_tags?search=$($Search -replace ' ', '%20')"
    $Tags | Sort-Object name | Select-Object id, name, count, link
}
#EndRegion '.\Private\Get-PSWeeklyTag.ps1' 32
#Region '.\Private\Get-WeeklyCategory.ps1' -1

<#
.SYNOPSIS
Retrieves the categories for PSWeekly links.
 
.DESCRIPTION
This function fetches the categories available for PSWeekly links.
 
.EXAMPLE
Get-PSWeeklyCategory
 
.NOTES
General notes
#>

function Get-PSWeeklyCategory {
    [CmdletBinding()]
    param ()

    $Categories= Invoke-PSWeeklyAPI -Path 'link_library_category?parent=0'
    $Categories | Where-Object{ $_.name -ne '_PSWeekly' } | Sort-Object name | Select-Object id, name
}

#EndRegion '.\Private\Get-WeeklyCategory.ps1' 22
#Region '.\Private\Invoke-PSWeeklyAPI.ps1' -1

<#
.SYNOPSIS
Invokes the PSWeekly API.
 
.DESCRIPTION
This function interacts with the PSWeekly API to retrieve data.
 
.PARAMETER Path
The API endpoint path to query.
 
.PARAMETER PerPage
The number of results to return per page.
 
.PARAMETER MaxResults
The maximum number of results to return.
 
.EXAMPLE
Invoke-PSWeeklyAPI -Path "posts" -PerPage 10 -MaxResults 100
 
.NOTES
General notes
#>

function Invoke-PSWeeklyAPI {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Path,

        [int]$PerPage = 10,  # WP API max per page is usually 100
        
        [int]$MaxResults = 0  # 0 means no limit, return all results
    )

    $results = @()
    $page = 1

    $Uri = "https://psweekly.dowst.dev/wp-json/wp/v2/$($Path.TrimStart('/'))"

    do {
        # Check if the base URI already has query parameters
        Write-Verbose "Build uri from: $Uri"
        if ($Uri -match '\?') {
            $fullUri = "$Uri&per_page=$PerPage&page=$page"
        }
        else {
            $fullUri = "$($Uri)?per_page=$PerPage&page=$page"
        }
        $response = $null
        try {
            Write-Verbose "Fetching data from: $fullUri"
            $response = Invoke-RestMethod -Uri $fullUri -ErrorAction Stop
        }
        catch {
            try {
                $d = $_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction Stop
            }
            catch {}
            if ($d.Code -ne 'rest_post_invalid_page_number') {
                Write-Error "Failed to fetch data from $fullUri. Error: $($_)"
            }
            $response = $null
        }
        if ($response.Count -gt 0) {
            $results += $response
            $page++
            
            # Check if we've reached the maximum results limit
            if ($MaxResults -gt 0 -and $results.Count -ge $MaxResults) {
                break
            }
        }
    } while ($response.Count -eq $PerPage)

    # If MaxResults is specified, trim the results to the exact limit
    if ($MaxResults -gt 0 -and $results.Count -gt $MaxResults) {
        $results = $results[0..($MaxResults - 1)]
    }

    return $results
}
#EndRegion '.\Private\Invoke-PSWeeklyAPI.ps1' 81
#Region '.\Public\Find-PSWeekly.ps1' -1

<#
.SYNOPSIS
Get a list of PSWeekly posts based on month and year.
 
.DESCRIPTION
This function retrieves PSWeekly posts filtered by a specific month and year. If the month is not set or set to 13, it retrieves all posts for the specified year.
 
.PARAMETER Month
Month to filter the results.
 
.PARAMETER Year
Year to filter the results.
 
.EXAMPLE
Find-PSWeekly -Month 5 -Year 2024
 
.NOTES
General notes
#>

function Find-PSWeekly {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [ValidateRange(1, 12)]
        [int]$Month = 13,  # 13 means no month filter
        [Parameter(Mandatory = $true)]
        [ValidateRange(2000, 2100)]
        [int]$Year
    )

    if ($Month -ne 13) {
        $monthName = (Get-Culture en-US).DateTimeFormat.GetMonthName($Month)
        $Search = "$monthName $Year"
    } else {
        $Search = "$Year"
    }
    $posts = Get-PSWeeklyPost -Search $Search -Last 52

    $posts
}
#EndRegion '.\Public\Find-PSWeekly.ps1' 41
#Region '.\Public\Get-PSWeekly.ps1' -1

<#
.SYNOPSIS
Returns the latest PSWeekly post and its associated links.
 
.DESCRIPTION
This function retrieves the latest PSWeekly post and its associated links.
 
.PARAMETER OpenBrowser
If specified, opens the post in a web browser.
 
.EXAMPLE
Get-PSWeekly -OpenBrowser
 
.NOTES
General notes
#>

function Get-PSWeekly {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [switch]$OpenBrowser = $false
    )
    
    $post = Get-PSWeeklyPost -Last 1
    
    if ($OpenBrowser) {
        Start-Process "https://psweekly.dowst.dev/?p=$($post.id)"
    }
    else {
        Get-PSWeeklyLinks -Tags $post.linktag -MaxResults 0
    }
}
#EndRegion '.\Public\Get-PSWeekly.ps1' 33
#Region '.\Public\Search-PSWeeklyLink.ps1' -1

<#
.SYNOPSIS
Searches for PSWeekly links based on specified criteria.
 
.DESCRIPTION
This function allows users to search for PSWeekly links using various parameters.
 
.PARAMETER Search
The search term to filter PSWeekly links.
 
.PARAMETER MaxResults
The maximum number of results to return. Results are returned in descending order by date. If set to 0, it returns all results.
 
.EXAMPLE
Search-PSWeeklyLink -Search "VSCode" -MaxResults 5
 
.NOTES
General notes
#>

function Search-PSWeeklyLink {
    [CmdletBinding(DefaultParameterSetName = 'Search')]
    param (
        [Parameter(Mandatory = $false, ParameterSetName = 'Search')]
        [string]$Search = $null,
        [Parameter(Mandatory = $false, ParameterSetName = 'Search')]
        [int]$MaxResults = 100
    )

    Get-PSWeeklyLinks -Search $Search -MaxResults $MaxResults
}
#EndRegion '.\Public\Search-PSWeeklyLink.ps1' 31