PSFPL.psm1

Write-Verbose 'Importing from [D:\a\1\s\PSFPL\Classes]'
# .PlayerTransformAttribute.ps1
class PlayerTransformAttribute : System.Management.Automation.ArgumentTransformationAttribute {
    [object] Transform([System.Management.Automation.EngineIntrinsics]$engineIntrinsics, [object]$inputData) {
        $outputData = switch ($inputData) {
            {$_ -is [string]} {
                [PSCustomObject]@{
                    Name = $_
                }
            }
            {$_ -is [int]} {
                [PSCustomObject]@{
                    PlayerId = $_
                }
            }
            {$_ -is [hashtable]} {[PSCustomObject]$_}
            {$_.PSTypeNames[0] -in 'FplPlayer', 'FplLineup'} {$_}
            default {throw 'You must provide a string, integer, hashtable or FPL player object.'}
        }
        foreach ($Player in $outputData) {
            if ($Player.PSTypeNames[0] -in 'FplPlayer', 'FplLineup') {
                continue
            }
            $RequiredProperties = '^Name$', '^PlayerId$'
            $ValidProperties = '^Name$', '^PlayerId$', '^Club$', '^Position$'

            if (-not ($Player.PSObject.Properties.Name -match ($RequiredProperties -join '|'))) {
                throw 'You must specify a Name or PlayerId.'
            }
            if ($Player.PSObject.Properties.Name -notmatch ($ValidProperties -join '|')) {
                throw 'Invalid properties found.'
            }

            if ($Player.Name) {
                $Player.Name = '^{0}$' -f $Player.Name
            }
        }
        return $outputData
    }
}

Write-Verbose 'Importing from [D:\a\1\s\PSFPL\Private]'
# .Assert-FplBankCheck.ps1
function Assert-FplBankCheck {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [PSObject[]]
        $PlayersIn,

        [Parameter(Mandatory)]
        [PSObject[]]
        $PlayersOut,

        [Parameter(Mandatory)]
        [int]
        $Bank
    )

    $PlayersInPrice = $PlayersIn.Price | Measure-Object -Sum
    $PlayersOutPrice = $PlayersOut.SellingPrice | Measure-Object -Sum
    $PriceDifference = $PlayersOutPrice.Sum - $PlayersInPrice.Sum
    $Balance = $Bank + [int]($PriceDifference * 10)
    if ($Balance -le 0) {
        $Message = 'Negative bank balance -{0}{1}m is not allowed' -f [char]163, - ($Balance / 10)
        Write-Error -Message $Message -ErrorAction 'Stop'
    }
}

# .Assert-FplLinup.ps1
function Assert-FplLineup {
    [CmdletBinding()]
    param (
        [PSTypeName('FplLineup')]
        [PSObject[]]
        $Lineup
    )

    $Players = @{
        GoalKeepers = $Lineup.Where{$_.Position -eq 'GoalKeeper'}
        Defenders   = $Lineup.Where{$_.Position -eq 'Defender'}
        Midfielders = $Lineup.Where{$_.Position -eq 'Midfielder'}
        Forwards    = $Lineup.Where{$_.Position -eq 'Forward'}
    }

    if ($Players['GoalKeepers'] -and $Players['GoalKeepers'].count -ne 1) {
        Write-Error -Message 'You must only have 1 goalkeeper' -ErrorAction 'Stop'
    }
    if ($Players['Defenders'].count -lt 3) {
        Write-Error -Message 'You must have more than 3 defenders' -ErrorAction 'Stop'
    }
    if ($Players['Midfielders'].count -lt 2) {
        Write-Error -Message 'You must have more than 2 midfielders' -ErrorAction 'Stop'
    }
    if (-not $Players['Forwards']) {
        Write-Error -Message 'You must have at least 1 forward' -ErrorAction 'Stop'
    }
}

# .ConvertTo-FplObject.ps1
function ConvertTo-FplObject {
    <#
    .SYNOPSIS
        Convert an object returned from the FPL API into a more PowerShelly object with a type name
    .DESCRIPTION
        Convert an object returned from the FPL API into a more PowerShelly object with a type name
    .PARAMETER InputObject
        A PowerShell object returned from the FPL API
    .PARAMETER Type
        The type name to give the resulted PowerShell object
    .EXAMPLE
        $Response = Invoke-RestMethod -Uri 'https://fantasy.premierleague.com/api/elements/' -UseBasicParsing
        ConvertTo-FplObject -InputObject $Response -Type 'FplPlayer'
    .LINK
        https://github.com/sk82jack/PSFPL/
    #>

    [CmdletBinding()]
    Param (
        [Parameter(Mandatory)]
        [object[]]
        $InputObject,

        [Parameter(Mandatory)]
        [ValidateSet(
            'FplPlayer', 'FplGameweek', 'FplFixture', 'FplLeagueTable', 'FplTeam',
            'FplLeague', 'FplTeamPlayer', 'FplLineup'
        )]
        [string]
        $Type
    )
    switch ($Type) {
        'FplPlayer' {
            $PositionHash = Get-FplElementType
            $TeamHash = Get-FplClubId
        }
        'FplGameweek' {
            $PlayerHash = Get-FplElementId
        }
        'FplFixture' {
            $TeamHash = Get-FplClubId
            $PlayerHash = Get-FplElementId
        }
        'FplLeagueTable' {
            $LeagueName = $InputObject[0].league.name
            $LeagueId = $InputObject[0].league.id
            $InputObject = $InputObject.foreach{$_.standings.results}
        }
        'FplTeam' {
            $TeamHash = Get-FplClubId
        }
        'FplLeague' {
            $InputObject = @($InputObject.classic) + @($InputObject.h2h).where{$_.name -and $_.name -ne 'cup'}
        }
        'FplTeamPlayer' {
            $Players = ConvertTo-FplObject -InputObject $Script:FplSessionData['Players'].Where{$_.id -in $InputObject.element} -Type FplPlayer
        }
        'FplLineup' {
            $Players = ConvertTo-FplObject -InputObject $Script:FplSessionData['Players'].Where{$_.id -in $InputObject.element} -Type FplPlayer
        }
    }

    $TextInfo = (Get-Culture).TextInfo
    foreach ($Object in $InputObject) {
        $Hashtable = [ordered]@{}
        $Object.psobject.properties | ForEach-Object {
            $Name = $TextInfo.ToTitleCase($_.Name) -replace '_' -replace 'Team', 'Club' -replace 'Entry', 'Team' -replace 'Event', 'Gameweek'
            $Value = if ($_.Value -is [string]) {
                if ($PSVersionTable.PSVersion.Major -lt 6) {
                    $DiacriticName = [Text.Encoding]::UTF8.GetString([Text.Encoding]::GetEncoding('ISO-8859-1').GetBytes($_.Value))
                }
                else {
                    $DiacriticName = $_.value
                }
                [Text.Encoding]::ASCII.GetString([Text.Encoding]::GetEncoding("Cyrillic").GetBytes($DiacriticName)).trim()
            }
            else {
                $_.Value
            }
            $Hashtable[$Name] = $Value
        }

        switch ($Type) {
            'FplPlayer' {
                $Hashtable['PlayerId'] = $Hashtable['Id']
                $Hashtable.Remove('Id')
                $Hashtable['Name'] = $Hashtable['WebName']
                $Hashtable['Position'] = $PositionHash[$Object.element_type]
                $Hashtable['ClubId'] = $Hashtable['Club']
                $Hashtable['Club'] = $TeamHash[$Hashtable['Club']]
                $Hashtable['Price'] = $Object.now_cost / 10
            }
            'FplGameweek' {
                $Hashtable['GameweekId'] = $Hashtable['Id']
                $Hashtable['Gameweek'] = $Hashtable['Id']
                $Hashtable.Remove('Id')
                $Hashtable['DeadlineTime'] = Get-Date $Object.deadline_time
                $Hashtable['BenchBoostPlays'] = $Hashtable['ChipPlays'].Where{$_.chip_name -eq 'bboost'}[0].num_played
                $Hashtable['TripleCaptainPlays'] = $Hashtable['ChipPlays'].Where{$_.chip_name -eq '3xc'}[0].num_played
                $Hashtable.Remove('ChipPlays')
                foreach ($Property in 'MostSelected', 'MostTransferredIn', 'TopElement', 'MostCaptained', 'MostViceCaptained') {
                    $Hashtable["$($Property)Id"] = $Hashtable[$Property]
                    if ($HashTable[$Property]) {
                        $Hashtable[$Property] = $PlayerHash[$HashTable[$Property]]
                    }
                }
                $Hashtable['TopElementPoints'] = if ($Hashtable['TopElementInfo']) {
                    $Hashtable['TopElementInfo'].points
                }
                $Hashtable.Remove('TopElementInfo')
            }
            'FplFixture' {
                $Hashtable['FixtureId'] = $Hashtable['Id']
                $Hashtable.Remove('Id')
                $Hashtable['DeadlineTime'] = try {
                    Get-Date $Object.deadline_time
                }
                catch {
                    'tbc'
                }
                $HashTable['KickoffTime'] = try {
                    Get-Date $Object.kickoff_time
                }
                catch {
                    'tbc'
                }
                if (-not $Hashtable['Gameweek']) {
                    $Hashtable['Gameweek'] = 'tbc'
                }
                $Hashtable['ClubA'] = $TeamHash[$Object.team_a]
                $Hashtable['ClubH'] = $TeamHash[$Object.team_h]
                $Hashtable['Stats'] = foreach ($Stat in $Hashtable['Stats']) {
                    $StatType = $TextInfo.ToTitleCase($Stat.PSObject.Properties.Name)
                    foreach ($Letter in 'a', 'h') {
                        $Club = 'Club{0}' -f $Letter.ToUpper()
                        foreach ($Item in $Stat.$StatType.$Letter) {
                            [pscustomobject]@{
                                'PlayerId'   = $Item.element
                                'PlayerName' = $PlayerHash[$Item.element]
                                'StatType'   = $StatType -replace '_'
                                'StatValue'  = $Item.value
                                'ClubName'   = $Hashtable[$club]
                            }
                        }
                    }
                }
            }
            'FplLeagueTable' {
                $Hashtable['LeagueName'] = $LeagueName
                $Hashtable['LeagueId'] = $LeagueId
                $Hashtable['TeamId'] = $Hashtable['Team']
                $Hashtable.Remove('Team')
            }
            'FplTeam' {
                if ($Hashtable['FavouriteClub']) {
                    $Hashtable['FavouriteClub'] = $TeamHash[$Hashtable['FavouriteClub']]
                }
                $Hashtable['TeamId'] = $Hashtable['Id']
                $Hashtable.Remove('Id')
                $Hashtable['JoinedTime'] = Get-Date $Object.joined_time
                $Hashtable.Remove('Leagues')
                $Hashtable['LastDeadlineBank'] = $Hashtable['LastDeadlineBank'] / 10
                $Hashtable['LastDeadlineValue'] = $Hashtable['LastDeadlineValue'] / 10
            }
            'FplLeague' {
                $Hashtable['LeagueId'] = $Hashtable['Id']
                $Hashtable.Remove('Id')
                $Hashtable['LeagueType'] = switch ($Hashtable['LeagueType']) {
                    'c' {'Public'}
                    's' {'Global'}
                    'x' {'Private'}
                }
                $Hashtable['Scoring'] = switch ($Hashtable['Scoring']) {
                    'c' {'Classic'}
                    'h' {'H2H'}
                }
                $Hashtable['Created'] = Get-Date $Hashtable['Created']
            }
            'FplTeamPlayer' {
                $Hashtable['PlayerId'] = $Hashtable['Element']
                $Hashtable.Remove('Element')
                if ($Hashtable.position -le 11) {
                    $Hashtable['PlayingStatus'] = 'Starting'
                }
                else {
                    $Hashtable['PlayingStatus'] = 'Substitute'
                }

                $CurrentPlayer = $Players.Where{$_.PlayerId -eq $Hashtable['PlayerId']}
                foreach ($Property in (Get-Member -InputObject $CurrentPlayer[0] -MemberType 'NoteProperty').Name) {
                    $Hashtable[$Property] = $CurrentPlayer.$Property
                }
                $Hashtable['Points'] = $CurrentPlayer.GameweekPoints * $Hashtable['Multiplier']
                $Hashtable.Remove('Multiplier')
                $Hashtable['NewsAdded'] = Get-Date $Hashtable['NewsAdded']
            }
            'FplLineup' {
                $Hashtable['PlayerId'] = $Hashtable['Element']
                $Hashtable.Remove('Element')
                $Hashtable['PositionNumber'] = $Hashtable['Position']
                if ($Hashtable.position -le 11) {
                    $Hashtable['PlayingStatus'] = 'Starting'
                    $Hashtable['IsSub'] = $false
                }
                else {
                    $Hashtable['PlayingStatus'] = 'Substitute'
                    $Hashtable['IsSub'] = $true
                }
                $CurrentPlayer = $Players.Where{$_.PlayerId -eq $Hashtable['PlayerId']}
                foreach ($Property in 'Name', 'Position', 'Club') {
                    $Hashtable[$Property] = $CurrentPlayer.$Property
                }
                $Hashtable['SellingPrice'] = $Hashtable['SellingPrice'] / 10
                $Hashtable['PurchasePrice'] = $Hashtable['PurchasePrice'] / 10
            }
        }
        $Hashtable['PsTypeName'] = $Type
        [pscustomobject]$Hashtable
    }
}

# .Find-FplPlayer.ps1
Function Find-FplPlayer {
    [CmdletBinding()]
    param (
        [Parameter()]
        [PSObject[]]
        $PlayerTransform,

        [Parameter()]
        [PSObject[]]
        $FplPlayerCollection
    )

    foreach ($Player in $PlayerTransform) {
        if ($Player.PSTypeNames -contains 'FplLineup') {
            $Player.PSObject.Copy()
            continue
        }
        $SearchName = $Player.Name -Replace '[.^$]'
        $PlayerObj = if ($Player.PlayerId) {
            $FplPlayerCollection.Where{$_.PlayerId -eq $Player.PlayerId}
        }
        else {
            $FplPlayerCollection.Where{
                $_.Name -match $Player.Name -and
                $_.Club -match $Player.Club -and
                $_.Position -match $Player.Position
            }
        }
        if (@($PlayerObj.Count) -gt 1) {
            $Message = 'Multiple players found that match "{0}"' -f $SearchName
            Write-Error -Message $Message -ErrorAction 'Stop'
        }

        if (-not $PlayerObj) {
            if ($FplPlayerCollection.Count -eq 15) {
                $Message = 'The player "{0}" cannot be found in your team.' -f $SearchName
            }
            else {
                $Message = 'The player "{0}" does not exist with the specified properties.' -f $SearchName
            }

            Write-Error -Message $Message -ErrorAction 'Stop'
        }

        $PlayerObj.PSObject.Copy()
    }
}

# .Get-ErrorResponsePayload.ps1
function Get-ErrorResponsePayload {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        $ErrorObject
    )
    if ($PSVersionTable.PSVersion.Major -lt 6) {
        if ($ErrorObject.Exception.Response) {
            $Reader = [System.IO.StreamReader]::new($ErrorObject.Exception.Response.GetResponseStream())
            $Reader.BaseStream.Position = 0
            $Reader.DiscardBufferedData()
            $ResponseBody = $Reader.ReadToEnd()
            $ResponseBody
        }
    }
    else {
        $ErrorObject.ErrorDetails.Message
    }
}

# .Get-FplClubId.ps1
function Get-FplClubId {
    <#
    .SYNOPSIS
        Retrieves a hashtable of club IDs to club names
    .DESCRIPTION
        Retrieves a hashtable of club IDs to club names
    .EXAMPLE
        Get-FplClubId
    .LINK
        https://github.com/sk82jack/PSFPL/
    #>

    [CmdletBinding()]
    param ()
    $Hashtable = @{}

    if ((-not $Script:FplSessionData) -or (-not $Script:FplSessionData['Clubs'])) {
        $Bootstrap = Invoke-RestMethod -Uri 'https://fantasy.premierleague.com/api/bootstrap-static/' -UseBasicParsing
        $Script:FplSessionData = @{
            ElementTypes = $Bootstrap.element_types
            Clubs        = $Bootstrap.teams
            Players      = $Bootstrap.elements
        }
    }

    foreach ($Club in $Script:FplSessionData['Clubs']) {
        $Hashtable[$Club.id] = $Club.name
    }
    $Hashtable
}

# .Get-FplElementId.ps1
function Get-FplElementId {
    <#
    .SYNOPSIS
        Retrieves a hashtable of player IDs to player names
    .DESCRIPTION
        Retrieves a hashtable of player IDs to player names
    .EXAMPLE
        Get-FplElementId
    .LINK
        https://github.com/sk82jack/PSFPL/
    #>

    [CmdletBinding()]
    param ()
    $Hashtable = @{}
    if ((-not $Script:FplSessionData) -or (-not $Script:FplSessionData['Players'])) {
        $Bootstrap = Invoke-RestMethod -Uri 'https://fantasy.premierleague.com/api/bootstrap-static/' -UseBasicParsing
        $Script:FplSessionData = @{
            ElementTypes = $Bootstrap.element_types
            Clubs        = $Bootstrap.teams
            Players      = $Bootstrap.elements
        }
    }
    foreach ($Element in $Script:FplSessionData['Players']) {
        $DiacriticName = [Text.Encoding]::UTF8.GetString([Text.Encoding]::GetEncoding('ISO-8859-1').GetBytes($Element.web_name))
        $Hashtable[$Element.id] = [Text.Encoding]::ASCII.GetString([Text.Encoding]::GetEncoding("Cyrillic").GetBytes($DiacriticName))
    }
    $Hashtable
}

# .Get-FplElementType.ps1
function Get-FplElementType {
    <#
    .SYNOPSIS
        Retrieves a hashtable of position IDs to position names
    .DESCRIPTION
        Retrieves a hashtable of position IDs to position names
    .EXAMPLE
        Get-FplElementType
    .LINK
        https://github.com/sk82jack/PSFPL/
    #>

    [CmdletBinding()]
    param ()
    $Hashtable = @{}
    if ((-not $Script:FplSessionData) -or (-not $Script:FplSessionData['ElementTypes'])) {
        $Bootstrap = Invoke-RestMethod -Uri 'https://fantasy.premierleague.com/api/bootstrap-static/' -UseBasicParsing
        $Script:FplSessionData = @{
            ElementTypes = $Bootstrap.element_types
            Clubs        = $Bootstrap.teams
            Players      = $Bootstrap.elements
        }
    }
    foreach ($Element in $Script:FplSessionData['ElementTypes']) {
        $Hashtable[$Element.id] = $Element.singular_name
    }
    $Hashtable
}

# .Get-FplSpentPoints.ps1
function Get-FplSpentPoints {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [int]
        $TransfersCount,

        [Parameter(Mandatory)]
        $TransfersInfo
    )

    if ($TransfersInfo.status -ne 'cost') {
        $SpentPoints = 0
    }
    else {
        $TransfersOverLimit = $TransfersCount - $TransfersInfo.limit + $TransfersInfo.made
        if ($TransfersOverLimit -gt 0) {
            $SpentPoints = $TransfersOverLimit * $TransfersInfo.cost
        }
        else {
            $SpentPoints = 0
        }
    }

    $SpentPoints
}

# .Get-FplTransfersInfo.ps1
function Get-FplTransfersInfo {
    [CmdletBinding()]
    param ()

    $Uri = 'https://fantasy.premierleague.com/api/my-team/{0}/' -f $Script:FplSessionData['TeamID']
    $Result = Invoke-RestMethod -Uri $Uri -UseBasicParsing -WebSession $Script:FplSessionData['FplSession']
    $Result.transfers
}

# .Invoke-FplLineupSwap.ps1
function Invoke-FplLineupSwap {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [PSTypeName('FplLineup')]
        [PSObject[]]
        $Lineup,

        [Parameter(Mandatory)]
        [PSTypeName('FplLineup')]
        [PSObject[]]
        $PlayersIn,

        [Parameter(Mandatory)]
        [PSTypeName('FplLineup')]
        [PSObject[]]
        $PlayersOut
    )

    $Def = '
        namespace FPL
        {
            namespace Player
            {
                public enum Position
                {
                    Goalkeeper = 0,
                    Defender = 100,
                    Midfielder = 200,
                    Forward = 300
                }
            }
        }
    '

    Add-Type -TypeDefinition $Def

    foreach ($Index in 0..($PlayersIn.Count - 1)) {
        $InPlayer = $PlayersIn[$Index].psobject.copy()
        $OutPlayer = $PlayersOut[$Index].psobject.copy()

        foreach ($Property in 'PlayerId', 'Position', 'Name') {
            $Lineup.Where{$_.PositionNumber -eq $OutPlayer.PositionNumber}[0].$Property = $InPlayer.$Property
            $Lineup.Where{$_.PositionNumber -eq $InPlayer.PositionNumber}[0].$Property = $OutPlayer.$Property
        }

        $IsSamePosition = $InPlayer.Position -eq $OutPlayer.Position
        $AreBothSubstitutes = $InPlayer.IsSub -and $OutPlayer.IsSub

        if ((-not $IsSamePosition) -and (-not $AreBothSubstitutes)) {
            $Starters, $Substitutes = $Lineup.Where( {-not $_.IsSub}, 'Split')
            $SortOrder = @(
                {[enum]::GetNames([FPL.Player.Position]).IndexOf($_)}
                {[Fpl.Player.Position]$_.Position + [int]$_.PositionNumber}
            )
            $NewStarters = $Starters | Sort-Object $SortOrder
            $Lineup = $NewStarters + $Substitutes
            $Counter = 1
            $Lineup.Foreach{
                $_.PositionNumber = $Counter
                $Counter++
            }
        }
    }

    $Lineup
}

# .Read-FplYesNoPrompt.ps1
function Read-FplYesNoPrompt {
    [CmdletBinding()]
    param (
        [Parameter()]
        [string]
        $Title,

        [Parameter()]
        [string]
        $Message,

        [Parameter()]
        [string]
        $YesMessage,

        [Parameter()]
        [string]
        $NoMessage
    )
    $Options = [System.Management.Automation.Host.ChoiceDescription[]](
        [System.Management.Automation.Host.ChoiceDescription]::new('&Yes', $YesMessage),
        [System.Management.Automation.Host.ChoiceDescription]::new('&No', $NoMessage)
    )
    $Host.UI.PromptForChoice($Title, $Message, $Options, 1)
}

# .Set-FplLineupCaptain.ps1
function Set-FplLineupCaptain {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [PSTypeName('FplLineup')]
        [PSObject[]]
        $Lineup,

        [Parameter()]
        [PSTypeName('FplLineup')]
        [PSObject]
        $Captain,

        [Parameter()]
        [PSTypeName('FplLineup')]
        [PSObject]
        $ViceCaptain
    )

    $OriginalCaptain = $Lineup.Where{$_.IsCaptain}[0]
    $OriginalViceCaptain = $Lineup.Where{$_.IsViceCaptain}[0]

    if ($Captain) {
        $OriginalCaptain.IsCaptain = $false
        $NewCaptain = $Lineup.Where{$_.PlayerId -eq $Captain.PlayerId}[0]
        if ($NewCaptain.IsSub) {
            Write-Error -Message "You cannot captain a substitute" -ErrorAction 'Stop'
        }
        if ($NewCaptain.IsViceCaptain) {
            $NewCaptain.IsViceCaptain = $false
            $OriginalCaptain.IsViceCaptain = $true
        }
        $NewCaptain.IsCaptain = $true
    }
    if ($ViceCaptain) {
        $OriginalViceCaptain.IsViceCaptain = $false
        $NewViceCaptain = $Lineup.Where{$_.PlayerId -eq $ViceCaptain.PlayerId}[0]
        if ($NewViceCaptain.IsSub) {
            Write-Error -Message "You cannot vice captain a substitute" -ErrorAction 'Stop'
        }
        if ($NewViceCaptain.IsCaptain) {
            $NewViceCaptain.IsCaptain = $false
            $OriginalViceCaptain.IsCaptain = $true
        }
        $NewViceCaptain.IsViceCaptain = $true
    }

    $Lineup
}

# .Write-FplError.ps1
function Write-FplError {
    [CmdletBinding()]
    param (
        [CmdletBinding()]
        $FplError
    )

    if ($FplError.details) {
        $FplError = $FplError.details
    }
    $Message = if ($FplError.non_form_errors) {
        [string]$FplError.non_form_errors
    }
    elseif ($FplError.non_field_errors) {
        [string]$FplError.non_field_errors
    }
    elseif ($FplError.errors) {
        switch ($FplError.errors) {
            {$_.freehit} {[string]$_.freehit}
            {$_.wildcard} {[string]$_.wildcard}
            default {[string]$_}
        }
    }
    else {
        [string]$_
    }

    $Message = $Message -replace '.*::(.*)', '$1'
    Write-Error -Message $Message -ErrorAction 'Stop'
}

Write-Verbose 'Importing from [D:\a\1\s\PSFPL\Public]'
# .Connect-FPL.ps1
function Connect-Fpl {
    <#
    .SYNOPSIS
        Connect to the FPL API using provided login credentials
    .DESCRIPTION
        Connect to the FPL API using provided login credentials
        This is required for some functions which pull data related to your account
    .PARAMETER Credential
        Your FPL credentials that you use to log into https://fantasy.premierleague.com
    .PARAMETER Force
        Create a connection regardless of whether there is already an existing connection
    .EXAMPLE
        Connect-FPL -Credential myname@email.com
    .EXAMPLE
        Connect-FPL -Credential myname@email.com -Force
    .LINK
        https://psfpl.readthedocs.io/en/master/functions/Connect-Fpl
    .LINK
        https://github.com/sk82jack/PSFPL/blob/master/PSFPL/Public/Connect-FPL.ps1
    #>

    [Cmdletbinding()]
    Param (
        [Parameter(Mandatory)]
        [pscredential]
        $Credential,

        [Parameter()]
        [switch]
        $Force
    )

    if ($Script:FplSessionData -and $Script:FplSessionData['FplSession'] -and (-not $Force)) {
        Write-Warning "A connection already exists. Use the Force parameter to connect."
        return
    }

    $Uri = 'https://users.premierleague.com/accounts/login/'
    $null = Invoke-WebRequest -Uri $Uri -SessionVariable 'FplSession' -Method 'Post' -UseBasicParsing -Body @{
        'login'        = $Credential.UserName
        'password'     = $Credential.GetNetworkCredential().Password
        'app'          = 'plfpl-web'
        'redirect_uri' = 'https://fantasy.premierleague.com/a/login'
    }

    $ManagerInfo = Invoke-RestMethod -Uri 'https://fantasy.premierleague.com/api/me/' -UseBasicParsing -WebSession $FplSession

    if (-not ($ManagerInfo.player)) {
        Throw 'Invalid credentials'
    }

    $Bootstrap = Invoke-RestMethod -Uri 'https://fantasy.premierleague.com/api/bootstrap-static/' -UseBasicParsing -WebSession $FplSession
    $TeamInfo = Invoke-RestMethod -Uri ('https://fantasy.premierleague.com/api/entry/{0}/' -f $ManagerInfo.player.entry) -UseBasicParsing -WebSession $FplSession

    $Script:FplSessionData = @{
        FplSession   = $FplSession
        TeamID       = $ManagerInfo.player.entry
        CurrentGW    = [int]$TeamInfo.current_event
        ElementTypes = $Bootstrap.element_types
        Clubs        = $Bootstrap.teams
        Players      = $Bootstrap.elements
    }
}

# .Get-FplFixture.ps1
function Get-FplFixture {
    <#
    .SYNOPSIS
        Retrieves a list of FPL fixtures
    .DESCRIPTION
        Retrieves a list of FPL fixtures
    .PARAMETER Gameweek
        Retrieve the fixtures from a specified gameweek
    .PARAMETER Club
        Retrieve the fixtures for a specified club
    .EXAMPLE
        Get-FplFixture
 
        This will list all of the fixtures throughout the season
    .EXAMPLE
        Get-FplFixture -Gameweek 14
 
        This will list the fixtures from gameweek 14
    .EXAMPLE
        Get-FplFixture -Club Liverpool
 
        This will list all of the fixtures for Liverpool FC
    .EXAMPLE
        Get-FplFixture -Club Chelsea -Gameweek 2
 
        This will get the Chelsea FC fixture in gameweek 2
    .LINK
        https://psfpl.readthedocs.io/en/master/functions/Get-FplFixture
    .LINK
        https://github.com/sk82jack/PSFPL/blob/master/PSFPL/Public/Get-FplFixture.ps1
    #>

    [CmdletBinding()]
    Param (
        [Parameter(ValueFromPipelineByPropertyName)]
        [int]
        $Gameweek,

        [Parameter()]
        [ValidateSet(
            'Arsenal', 'Bournemouth', 'Brighton', 'Burnley', 'Cardiff', 'Chelsea', 'Crystal Palace',
            'Everton', 'Fulham', 'Huddersfield', 'Leicester', 'Liverpool', 'Man City', 'Man Utd',
            'Newcastle', 'Southampton', 'Spurs', 'Watford', 'West Ham', 'Wolves'
        )]
        [string]
        $Club
    )

    process {
        $Response = Invoke-RestMethod -Uri 'https://fantasy.premierleague.com/api/fixtures/' -UseBasicParsing
        if ($Response -match 'The game is being updated.') {
            Write-Warning 'The game is being updated. Please try again shortly.'
            return
        }
        $Fixtures = ConvertTo-FplObject -InputObject $Response -Type 'FplFixture' | Sort-Object Gameweek, KickOffTime
        $Fixtures.Where{
            ($Gameweek -eq 0 -or $_.Gameweek -eq $Gameweek) -and
            $_.ClubH + $_.ClubA -match $Club
        }
    }
}

# .Get-FplGameweek.ps1
function Get-FplGameweek {
    <#
    .SYNOPSIS
        Retrieves a list of FPL gameweeks
    .DESCRIPTION
        Retrieves a list of FPL gameweeks
    .PARAMETER Gameweek
        Retrieve a specific gameweek by it's number
    .Parameter Current
        Retrieves the current gameweek
    .EXAMPLE
        Get-FplGameweek
 
        This will list all of the gameweeks
    .EXAMPLE
        Get-FplGameweek -Gameweek 14
 
        This will list only gameweek 14
    .EXAMPLE
        9 | Get-FplGameweek
 
        This will list only gameweek 9
    .EXAMPLE
        Get-FplGameweek -Current
 
        This will list only the current gameweek
    .LINK
        https://psfpl.readthedocs.io/en/master/functions/Get-FplGameweek
    .LINK
        https://github.com/sk82jack/PSFPL/blob/master/PSFPL/Public/Get-FplGameweek.ps1
    #>

    [CmdletBinding(DefaultParameterSetName = 'Gameweek')]
    param (
        [Parameter(
            ParameterSetName = 'Gameweek',
            ValueFromPipeline
        )]
        [int]
        $Gameweek,

        [Parameter(ParameterSetName = 'Current')]
        [switch]
        $Current
    )

    $Response = Invoke-RestMethod -Uri 'https://fantasy.premierleague.com/api/bootstrap-static/' -UseBasicParsing
    if ($Response -match 'The game is being updated.') {
        Write-Warning 'The game is being updated. Please try again shortly.'
        return
    }
    $Gameweeks = ConvertTo-FplObject -InputObject $Response.events -Type 'FplGameweek'

    if ($Current) {
        $Gameweeks.Where{$_.IsCurrent}
    }
    elseif ($Gameweek -gt 0) {
        $Gameweeks.Where{$_.Gameweek -eq $Gameweek}
    }
    else {
        $Gameweeks
    }
}

# .Get-FplLeague.ps1
function Get-FplLeague {
    <#
    .SYNOPSIS
        Lists the leagues that a team is a member of.
    .DESCRIPTION
        Lists the leagues that a team is a member of given a team ID.
        If no team ID is specified then it will list the leagues for your team. This requires an existing connection which can be created with Connect-Fpl.
        If there is no existing connection found it will prompt for credentials.
    .PARAMETER TeamId
        The team ID of the team to list the leagues of.
    .EXAMPLE
        Get-FplLeague
 
        This will list the leagues that your team is in.
    .EXAMPLE
        Get-FplLeague -TeamId 12345
 
        This will list the leagues that the team with the team ID of 12345 is in.
    .LINK
        https://psfpl.readthedocs.io/en/master/functions/Get-FplLeague
    .LINK
        https://github.com/sk82jack/PSFPL/blob/master/PSFPL/Public/Get-FplLeague.ps1
    #>


    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline)]
        [int]
        $TeamId
    )

    process {
        if ($TeamId -eq 0) {
            if ((-not $Script:FplSessionData) -or (-not $Script:FplSessionData['FplSession'])) {
                Write-Warning 'No existing connection found'
                $Credential = Get-Credential -Message 'Please enter your FPL login details'
                Connect-Fpl -Credential $Credential
            }

            $TeamId = $Script:FplSessionData['TeamID']
        }

        $Response = Invoke-RestMethod -Uri "https://fantasy.premierleague.com/api/entry/$TeamId/" -UseBasicParsing
        if ($Response -match 'The game is being updated.') {
            Write-Warning 'The game is being updated. Please try again shortly.'
            return
        }
        ConvertTo-FplObject -InputObject $Response.leagues -Type 'FplLeague'
    }
}

# .Get-FplLeagueTable.ps1
function Get-FplLeagueTable {
    <#
    .SYNOPSIS
        Retrieves an FPL league table
    .DESCRIPTION
        Retrieves an FPL league table given a league ID and league type
    .PARAMETER LeagueId
        An FPL league Id
    .Parameter Type
        An FPL league type. This can either be 'Classic' or 'HeadToHead'
    .Parameter League
        This parameter allows you to pass in an FplLeague object directly which can be retrieved from Get-FplLeague
    .EXAMPLE
        Get-FplLeagueTable -Id 12345 -Type Classic
 
        This will show the league standings for the classic league of ID 12345
    .EXAMPLE
        Get-FplLeague | Where Name -eq 'My League' | Get-FplLeagueTable
 
        This will get the league standings for the league that your team is in called 'My League'
    .LINK
        https://psfpl.readthedocs.io/en/master/functions/Get-FplLeagueTable
    .LINK
        https://github.com/sk82jack/PSFPL/blob/master/PSFPL/Public/Get-FplLeagueTable.ps1
    #>

    [CmdletBinding(DefaultParameterSetName = 'Default')]
    param (
        [Parameter(
            Mandatory,
            ParameterSetName = 'Default'
        )]
        [int]
        $LeagueId,

        [Parameter(
            Mandatory,
            ParameterSetName = 'Default'
        )]
        [ValidateSet('Classic', 'H2H')]
        [string]
        $Type,

        [Parameter(
            Mandatory,
            ValueFromPipeline,
            ParameterSetName = 'PipelineInput'
        )]
        [PSTypeName('FplLeague')]
        $League
    )
    begin {
        if ((-not $Script:FplSessionData) -or (-not $Script:FplSessionData['FplSession'])) {
            Write-Warning 'You must be logged in to view leagues'
            $Credential = Get-Credential -Message 'Please enter your FPL login details'
            Connect-Fpl -Credential $Credential
        }
    }
    process {
        if ($PSCmdlet.ParameterSetName -eq 'PipelineInput') {
            $Type = $League.Scoring
            $LeagueId = $League.LeagueId
        }
        $Results = do {
            $Page += 1
            $Url = 'https://fantasy.premierleague.com/api/leagues-{0}/{1}/standings/?page_new_entries=1&page_standings={2}' -f $Type.ToLower(), $LeagueId, $Page
            try {
                $Response = Invoke-RestMethod -Uri $Url -UseBasicParsing -WebSession $Script:FplSessionData['FplSession']
            }
            catch {
                Write-Warning "A $Type league with ID $LeagueId does not exist"
                return
            }
            if ($Response -match 'The game is being updated.') {
                Write-Warning 'The game is being updated. Please try again shortly.'
                return
            }
            $Response
        }
        until (-not $Response.standings.has_next)

        ConvertTo-FplObject -InputObject $Results -Type 'FplLeagueTable'
    }
}

# .Get-FplLineup.ps1
function Get-FplLineup {
    <#
    .SYNOPSIS
        Retrieves your team lineup for the upcoming gameweek
    .DESCRIPTION
        Retrieves your team lineup for the upcoming gameweek
    .EXAMPLE
        Get-FplLineup
    .LINK
        https://psfpl.readthedocs.io/en/master/functions/Get-FplLineup
    .LINK
        https://github.com/sk82jack/PSFPL/blob/master/PSFPL/Public/Get-FplLineup.ps1
    #>


    [CmdletBinding()]
    param()

    if ((-not $Script:FplSessionData) -or (-not $Script:FplSessionData['FplSession'])) {
        Write-Warning 'No existing connection found'
        $Credential = Get-Credential -Message 'Please enter your FPL login details'
        Connect-Fpl -Credential $Credential
    }

    $TeamId = $Script:FplSessionData['TeamID']

    $Response = Invoke-RestMethod -Uri "https://fantasy.premierleague.com/api/my-team/$TeamId/" -WebSession $FplSessionData['FplSession'] -UseBasicParsing

    ConvertTo-FplObject -InputObject $Response.Picks -Type 'FplLineup'
}

# .Get-FplPlayer.ps1
function Get-FplPlayer {
    <#
    .SYNOPSIS
        Retrieve a list of FPL players
    .DESCRIPTION
        Retrieve a list of FPL players
    .PARAMETER Name
        Filter players based on their surname
    .PARAMETER Position
        Filter players based on their position
    .PARAMETER Club
        Filter players based on their club
    .PARAMETER MaxPrice
        Filter players based on their price
    .PARAMETER DreamTeam
        Show the current dream team
    .EXAMPLE
        Get-FplPlayer
 
        Retrieve all of the FPL players in the game
    .EXAMPLE
        Get-FplPlayer -Name Hazard
 
        Retrieve all of the FPL players with 'Hazard' in their name
    .EXAMPLE
        Get-FplPlayer -Position Forward -Club 'Man City'
 
        Retrieve all of the forwards that play for Man City
    .EXAMPLE
        Get-FplPlayer -MaxPrice 5.1
 
        Retrieve all players priced at �5.1m or lower
    .EXAMPLE
        Get-FplPlayer -DreamTeam
 
        Retrieve the current dream team
    .LINK
        https://psfpl.readthedocs.io/en/master/functions/Get-FplPlayer
    .LINK
        https://github.com/sk82jack/PSFPL/blob/master/PSFPL/Public/Get-FplPlayer.ps1
    #>

    [CmdletBinding(DefaultParameterSetName = 'Filter')]
    Param (
        [Parameter(
            ParameterSetName = 'Filter',
            ValueFromPipeline,
            Position = 0
        )]
        [SupportsWildcards()]
        [string]
        $Name = '*',

        [Parameter(ParameterSetName = 'Filter')]
        [ValidateSet('Forward', 'Midfielder', 'Defender', 'Goalkeeper')]
        [string]
        $Position,

        [Parameter(ParameterSetName = 'Filter')]
        [ValidateSet(
            'Arsenal', 'Bournemouth', 'Brighton', 'Burnley', 'Cardiff', 'Chelsea', 'Crystal Palace',
            'Everton', 'Fulham', 'Huddersfield', 'Leicester', 'Liverpool', 'Man City', 'Man Utd',
            'Newcastle', 'Southampton', 'Spurs', 'Watford', 'West Ham', 'Wolves'
        )]
        [string]
        $Club,

        [Parameter(ParameterSetName = 'Filter')]
        [double]
        $MaxPrice = [int]::MaxValue,

        [Parameter(ParameterSetName = 'DreamTeam')]
        [switch]
        $DreamTeam
    )
    Begin {
        $Response = Invoke-RestMethod -Uri 'https://fantasy.premierleague.com/api/bootstrap-static/' -UseBasicParsing
        if ($Response -match 'The game is being updated.') {
            Write-Warning 'The game is being updated. Please try again shortly.'
            return
        }
        $Players = ConvertTo-FplObject -InputObject $Response.elements -Type 'FplPlayer' | Sort-Object TotalPoints, Price -Descending
    }
    Process {
        $Name = '^{0}$' -f ($Name -replace '\*', '.*')
        $Output = $Players.Where{
            $_.Name -match $Name -and
            $_.Position -match $Position -and
            $_.Club -match $Club -and
            $_.Price -le $MaxPrice
        }
        if ($PSBoundParameters.ContainsKey('MaxPrice')) {
            $Output = $Output | Sort-Object Price, TotalPoints -Descending
        }
        if ($DreamTeam) {
            $SortOrder = 'Goalkeeper', 'Defender', 'Midfielder', 'Forward'
            $Output = $Players.Where{$_.InDreamClub -eq $true} | Sort-Object {$SortOrder.IndexOf($_.Position)}
        }
        $Output
    }
    End {}
}

# .Get-FplTeam.ps1
function Get-FplTeam {
    <#
    .SYNOPSIS
        Retrieve information about an FPL team
    .DESCRIPTION
        Retrieve information about an FPL team either by a specified team ID or get your own team by logging in with Connect-FPL
    .PARAMETER TeamId
        A team ID to retrieve information about
    .EXAMPLE
        Connect-Fpl -Email MyEmail@hotmail.com
        $MyTeam = Get-FplTeam
 
        Retrieves information about your own team
    .EXAMPLE
        Get-FplTeam -TeamId 12345
 
        Retrieves information about the team with the ID 12345
    .LINK
        https://psfpl.readthedocs.io/en/master/functions/Get-FplTeam
    .LINK
        https://github.com/sk82jack/PSFPL/blob/master/PSFPL/Public/Get-FplTeam.ps1
    #>

    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline)]
        [int]
        $TeamId
    )
    process {
        if ($TeamId -eq 0) {
            if ((-not $Script:FplSessionData) -or (-not $Script:FplSessionData['FplSession'])) {
                Write-Warning 'No existing connection found'
                $Credential = Get-Credential -Message 'Please enter your FPL login details'
                Connect-Fpl -Credential $Credential
            }
            $TeamId = $Script:FplSessionData['TeamID']
        }
        $Response = Invoke-RestMethod -Uri "https://fantasy.premierleague.com/api/entry/$TeamId/" -UseDefaultCredentials
        if ($Response -match 'The game is being updated.') {
            Write-Warning 'The game is being updated. Please try again shortly.'
            return
        }
        ConvertTo-FplObject -InputObject $Response -Type 'FplTeam'
    }
}

# .Get-FplTeamPlayer.ps1
function Get-FplTeamPlayer {
    <#
    .SYNOPSIS
        Retrieves the player information within a team from a certain gameweek.
    .DESCRIPTION
        Retrieves the player information within a team from a certain gameweek.
        If no team ID or gameweek is supplied it will request to authenticate to get the users team and current gameweek.
    .PARAMETER TeamId
        The ID of the team to retrieve the player information
    .PARAMETER Gameweek
        The gameweek of which to retrieve the player information from
    .EXAMPLE
        Get-FplTeamPlayer
 
        This will prompt the user to supply credentials and return the user's player information from the current gameweek
    .EXAMPLE
        Get-FplTeamPlayer -TeamID 12345
 
        This will get the player information for the team with ID 12345 from the current gameweek
    .EXAMPLE
        Get-FplTeamPlayer -TeamID 12345 -Gameweek 12
 
        This will get the player information for the team with ID 12345 from gameweek 12
    .LINK
        https://psfpl.readthedocs.io/en/master/functions/Get-FplTeamPlayer
    .LINK
        https://github.com/sk82jack/PSFPL/blob/master/PSFPL/Public/Get-FplTeamPlayer.ps1
    #>


    [CmdletBinding()]
    param (
        [Parameter()]
        [int]
        $TeamId,

        [Parameter()]
        [int]
        $Gameweek
    )
    process {
        if ($TeamId -eq 0) {
            if ((-not $Script:FplSessionData) -or (-not $Script:FplSessionData['FplSession'])) {
                Write-Warning 'No existing connection found'
                $Credential = Get-Credential -Message 'Please enter your FPL login details'
                Connect-Fpl -Credential $Credential
            }

            $TeamId = $Script:FplSessionData['TeamID']
        }

        if ($Script:FplSessionData) {
            [int]$CurrentGameweek = $Script:FplSessionData['CurrentGW']
        }
        else {
            [int]$CurrentGameweek = (Get-FplGameweek -Current).Gameweek
        }

        if (($CurrentGameweek -eq 0) -or ($Gameweek -gt $CurrentGameweek)) {
            Write-Error -Message 'Cannot view team because the gameweek has not started yet' -ErrorAction 'Stop'
        }

        if ($Gameweek -eq 0) {
            $Gameweek = $CurrentGameweek
        }

        try {
            $Response = Invoke-RestMethod -Uri "https://fantasy.premierleague.com/api/entry/$TeamId/event/$Gameweek/picks/"
        }
        catch {
            if ($_.ErrorDetails.Message -eq '{"detail":"Not found."}') {
                Write-Error -Message "Team did not exist in gameweek $Gameweek" -ErrorAction 'Stop'
            }
            else {
                Write-Error -ErrorRecord $_ -ErrorAction 'Stop'
            }
        }
        if ($Response -match 'The game is being updated.') {
            Write-Warning 'The game is being updated. Please try again shortly.'
            return
        }
        ConvertTo-FplObject -InputObject $Response.picks -Type 'FplTeamPlayer'
    }
}

# .Invoke-FplTransfer.ps1
function Invoke-FplTransfer {
    <#
    .SYNOPSIS
        Makes a transfer for the upcoming gameweek
    .DESCRIPTION
        Makes a transfer for the upcoming gameweek
    .PARAMETER PlayersIn
        The player(s) which you wish to transfer into your team.
        This parameter takes multiple types of input:
            It can be passed as a string
            `'Salah'`
 
            It can be passed as a player ID
            `253`
 
            It can be passed as a hashtable of properties i.e.
            `@{Name = 'Salah'; Club = 'Liverpool'; Position = 'Midfeilder'; PlayerID = 253}`
            The only allowed properties are Name, Club, Position, PlayerID
 
            It can be the output of Get-FplPlayer or Get-FplLineup
    .PARAMETER PlayersOut
        The player(s) which you wish to transfer out of your team.
        This parameter takes multiple types of input:
            It can be passed as a string
            `'Salah'`
 
            It can be passed as a player ID
            `253`
 
            It can be passed as a hashtable of properties i.e.
            `@{Name = 'Salah'; Club = 'Liverpool'; Position = 'Midfeilder'; PlayerID = 253}`
            The only allowed properties are Name, Club, Position, PlayerID
 
            It can be the output of Get-FplPlayer or Get-FplLineup
    .PARAMETER ActivateChip
        Use this parameter to activate your Wildcard or Free Hit
    .PARAMETER Force
        By default this function will do a confirmation prompt.
        If you wish to suppress this prompt use the Force parameter.
    .EXAMPLE
        Invoke-FplTransfer -PlayersIn Hazard -PlayersOut Salah
 
        This example just uses the players names to identify them.
    .EXAMPLE
        Invoke-FplTransfer -PlayersIn Hazard, Robertson -PlayersOut Salah, Alonso
 
        This example demonstrates passing multiple players to the parameters
    .EXAMPLE
        Invoke-FplTransfer -PlayersIn 122 -PlayersOut 253
 
        This example uses the player IDs to identify them. 122 is Hazard and 253 is Salah.
        You can find a player ID by doing `Get-FplPlayer Hazard | Select PlayerID`
    .EXAMPLE
        Invoke-FplTransfer -PlayersIn @{Name = 'Sterling'; Club = 'Man City'} -PlayersOut Mane
 
        This example uses a hashtable to identify Sterling because there is another player in
        the game called Sterling who plays for Spurs.
    .EXAMPLE
        $Hazard = Get-FplPlayer -Name 'Hazard'
        $Salah = Get-FplLineup | Where Name -eq 'Salah'
        Invoke-FplTransfer -PlayersIn $Hazard -PlayersOut $Salah
 
        This example shows that you can use the objects directly from Get-FplPlayer and Get-FplLineup
    .EXAMPLE
        Invoke-FplTransfer -PlayersIn Hazard, Robertson -PlayersOut Salah, Alonso -ActivateChip Wildcard
 
        This example shows how to activate your Wildcard
    .EXAMPLE
        Invoke-FplTransfer -PlayersIn Hazard, Robertson -PlayersOut Salah, Alonso -ActivateChip FreeHit
 
        This example shows how to activate your Free Hit
    .LINK
        https://psfpl.readthedocs.io/en/master/functions/Invoke-FplTransfer
    .LINK
        https://github.com/sk82jack/PSFPL/blob/master/PSFPL/Public/Invoke-FplTransfer.ps1
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [PlayerTransformAttribute()]
        $PlayersIn,

        [Parameter(Mandatory)]
        [PlayerTransformAttribute()]
        $PlayersOut,

        [Parameter()]
        [ValidateSet('WildCard', 'FreeHit')]
        $ActivateChip,

        [Parameter()]
        [switch]
        $Force
    )

    if ($PlayersIn.Count -ne $PlayersOut.Count) {
        Write-Error 'You must provide the same number of players coming into the squad as there are players going out.' -ErrorAction 'Stop'
    }

    if ((-not $Script:FplSessionData) -or (-not $Script:FplSessionData['FplSession'])) {
        Write-Warning 'No existing connection found'
        $Credential = Get-Credential -Message 'Please enter your FPL login details'
        Connect-Fpl -Credential $Credential
    }

    $AllPlayers = ConvertTo-FplObject -InputObject $Script:FplSessionData['Players'] -Type 'FplPlayer'
    $Lineup = Get-FplLineup

    $InPlayers = Find-FplPlayer -PlayerTransform $PlayersIn -FplPlayerCollection $AllPlayers
    foreach ($Player in $InPlayers) {
        if ($Player.PlayerId -in $Lineup.PlayerId) {
            $Message = 'The player "{0}" is already in your team' -f $Player.Name
            Write-Error -Message $Message -ErrorAction 'Stop'
        }
    }
    $OutPlayers = Find-FplPlayer -PlayerTransform $PlayersOut -FplPlayerCollection $Lineup

    $TransfersInfo = Get-FplTransfersInfo
    Assert-FplBankCheck -PlayersIn $InPlayers -PlayersOut $OutPlayers -Bank $TransfersInfo.bank
    $SpentPoints = Get-FplSpentPoints -TransfersCount $InPlayers.Count -TransfersInfo $TransfersInfo

    if (-not $Force) {
        Write-Host -Object ('PlayersIn : {0}' -f ($InPlayers.Name -join ', '))
        Write-Host -Object ('PlayersOut : {0}' -f ($OutPlayers.Name -join ', '))
        $WriteHostSplat = @{
            Object = 'PointsHit : {0}' -f $SpentPoints
        }
        if ($SpentPoints -gt 0) {
            $WriteHostSplat['ForeGroundColor'] = 'Red'
        }
        Write-Host @WriteHostSplat
        if ($ActivateChip) {
            Write-Host -Object ('Chip : {0}' -f $ActivateChip)
        }

        $PromptParams = @{
            Title      = 'Confirm Transfers'
            Message    = 'Are you sure you wish to make the transfer(s) listed above?'
            YesMessage = 'Confirm transfers'
            NoMessage  = 'Change transfers'
        }
        $Answer = Read-FplYesNoPrompt @PromptParams

        if ($Answer -eq 1) {
            return
        }
    }

    $Body = @{
        chip      = $ActivateChip
        entry     = $Script:FplSessionData['TeamID']
        event     = $Script:FplSessionData['CurrentGW'] + 1
        transfers = [System.Collections.Generic.List[Hashtable]]::new()
    }

    foreach ($Index in 0..(@($InPlayers).Count - 1)) {
        $Body.transfers.Add(
            @{
                element_in     = $InPlayers[$Index].PlayerId
                element_out    = $OutPlayers[$Index].PlayerId
                purchase_price = $InPlayers[$Index].Price * 10
                selling_price  = $OutPlayers[$Index].SellingPrice * 10
            }
        )
    }

    $Params = @{
        Uri             = "https://fantasy.premierleague.com/api/transfers/"
        UseBasicParsing = $true
        WebSession      = $FplSessionData['FplSession']
        Method          = 'Post'
        Body            = ($Body | ConvertTo-Json)
        Headers         = @{
            'Content-Type' = 'application/json'
            'Referer'      = 'https://fantasy.premierleague.com/transfers'
        }
    }
    try {
        $Response = Invoke-RestMethod @Params -ErrorAction 'Stop'
    }
    catch {
        $Response = Get-ErrorResponsePayload -ErrorObject $_ | ConvertFrom-Json
        Write-FplError -FplError $Response
    }
}

# .Set-FplLineup.ps1
function Set-FplLineup {
    <#
    .SYNOPSIS
        Set your team lineup for the upcoming gameweek
    .DESCRIPTION
        Set your team lineup for the upcoming gameweek
    .PARAMETER PlayersIn
        The players which you wish to bring in to the starting XI.
        Alternatively, if you are just swapping the order of players on your bench then use PlayersIn for one bench player and PlayersOut for the other
        This parameter takes multiple types of input:
            It can be passed as a string
            `'Salah'`
 
            It can be passed as a player ID
            `253`
 
            It can be passed as a hashtable of properties i.e.
            `@{Name = 'Salah'; Club = 'Liverpool'; Position = 'Midfeilder'; PlayerID = 253}`
            The only allowed properties are Name, Club, Position, PlayerID
 
            It can be the output of Get-FplPlayer or Get-FplLineup
    .PARAMETER PlayersOut
        The players you wish to remove from the starting XI.
        Alternatively, if you are just swapping the order of players on your bench then use
        PlayersIn for one bench player and PlayersOut for the other.
        This parameter takes multiple types of input:
            It can be passed as a string
            `'Salah'`
 
            It can be passed as a player ID
            `253`
 
            It can be passed as a hashtable of properties i.e.
            `@{Name = 'Salah'; Club = 'Liverpool'; Position = 'Midfeilder'; PlayerID = 253}`
            The only allowed properties are Name, Club, Position, PlayerID
 
            It can be the output of Get-FplPlayer or Get-FplLineup
    .PARAMETER Captain
        The player who you wish to be Captain of your team
    .PARAMETER ViceCaptain
        The player who you wish to be ViceCaptain of your team
    .EXAMPLE
        Set-FplLineup -PlayersIn Son -PlayersOut Pogba
 
        This will remove Pogba from the starting XI and swap him with Son
    .EXAMPLE
        Set-FplLineup -PlayersIn Son, Rashford, Alexander-Arnold -PlayersOut Ings, Diop, Digne
 
        This will remove Ings, Diop and Digne from the starting XI and swap them with Son, Rashford and Alexander-Arnold
    .EXAMPLE
        Set-FplLineup -PlayersIn @{Name = 'Sterling'; Club = 'Man City'} -PlayersOut Mane
 
        You can use a hashtable to identify players if you have two players with the same name in your team.
    .EXAMPLE
        Set-FplLineup -Captain Salah
 
        This will set your captain for the upcoming gameweek to Salah
    .EXAMPLE
        Set-FplLineup -ViceCaptain Sterling
 
        This will set your vice captain for the upcoming gameweek to Sterling
    .EXAMPLE
        Set-FplLinup -PlayersIn Sane -PlayersOut Diop -Captain Sane -ViceCaptain Pogba
 
        This will swap out Diop for Sane in your starting XI and then set Sane to be the captain and Pogba to be the vice captain
    .LINK
        https://psfpl.readthedocs.io/en/master/functions/Set-FplLineup
    .LINK
        https://github.com/sk82jack/PSFPL/blob/master/PSFPL/Public/Set-FplLineup.ps1
    #>


    [CmdletBinding()]
    param (
        [Parameter()]
        [ValidateCount(0, 4)]
        [PlayerTransformAttribute()]
        [PSObject[]]
        $PlayersIn,

        [Parameter()]
        [ValidateCount(0, 4)]
        [PlayerTransformAttribute()]
        [PSObject[]]
        $PlayersOut,

        [Parameter()]
        [PlayerTransformAttribute()]
        $Captain,

        [Parameter()]
        [PlayerTransformAttribute()]
        $ViceCaptain
    )

    if (-not ($PlayersIn -or $PlayersOut -or $Captain -or $ViceCaptain)) {
        Write-Error -Message 'Please specify a change to make' -ErrorAction 'Stop'
    }

    if ($PlayersIn.Count -ne $PlayersOut.Count) {
        Write-Error 'You must provide the same number of players coming into the starting lineup as there are players going out' -ErrorAction 'Stop'
    }

    if ((-not $Script:FplSessionData) -or (-not $Script:FplSessionData['FplSession'])) {
        Write-Warning 'No existing connection found'
        $Credential = Get-Credential -Message 'Please enter your FPL login details'
        Connect-Fpl -Credential $Credential
    }

    $Lineup = Get-FplLineup

    $Players = [PSCustomObject]@{
        In          = Find-FplPlayer -PlayerTransform $PlayersIn -FplPlayerCollection $Lineup
        Out         = Find-FplPlayer -PlayerTransform $PlayersOut -FplPlayerCollection $Lineup
        Captain     = Find-FplPlayer -PlayerTransform $Captain -FplPlayerCollection $Lineup
        ViceCaptain = Find-FplPlayer -PlayerTransform $ViceCaptain -FplPlayerCollection $Lineup
    }

    $PlayerCollection = @($Players.In) + @($Players.Out) + @($Players.Captain, $Players.ViceCaptain) | Where-Object {$_}
    foreach ($Player in $PlayerCollection) {
        if ($Player.PlayerId -notin $Lineup.PlayerId) {
            $Message = 'There is no player with the name "{0}" in your team' -f $Player.Name
            Write-Error -Message $Message -ErrorAction 'Stop'
        }
    }
    if ($Players.In -and $Players.Out) {
        $Lineup = Invoke-FplLineupSwap -Lineup $Lineup -PlayersIn $Players.In -PlayersOut $Players.Out
    }
    if ($Captain -or $ViceCaptain) {
        $Lineup = Set-FplLineupCaptain -Lineup $Lineup -Captain $Players.Captain -ViceCaptain $Players.ViceCaptain
    }

    Assert-FplLineup -Lineup $Lineup.Where{-not $_.IsSub}

    $Body = [PSCustomObject]@{
        picks = $Lineup.Foreach{
            @{
                element         = $_.PlayerId
                position        = $_.PositionNumber
                is_captain      = $_.IsCaptain
                is_vice_captain = $_.IsViceCaptain
            }
        }
    }
    $TeamId = $Script:FplSessionData['TeamID']
    $Params = @{
        Uri             = "https://fantasy.premierleague.com/api/my-team/$TeamId/"
        UseBasicParsing = $true
        WebSession      = $FplSessionData['FplSession']
        Method          = 'Post'
        Body            = ($Body | ConvertTo-Json)
        Headers         = @{
            'Content-Type'     = 'application/json; charset=UTF-8'
            'X-Requested-With' = 'XMLHttpRequest'
            'Referer'          = 'https://fantasy.premierleague.com/a/team/my'
        }
    }

    try {
        $Response = Invoke-RestMethod @Params -ErrorAction 'Stop'
    }
    catch {
        $Response = Get-ErrorResponsePayload -ErrorObject $_ | ConvertFrom-Json
        Write-FplError -FplError $Response
    }
}