Functions/GenXdev.Queries.Webbrowser/Open-AllYoutubeVideos.ps1

###############################################################################

<#
.SYNOPSIS
Opens and controls YouTube videos in a browser window with keyboard shortcuts.
 
.DESCRIPTION
Performs an infinite auto opening YouTube search in a new fullscreen browser
window on second monitor. The console window shows video info and keyboard
shortcuts for controlling currently playing video.
 
.PARAMETER Queries
The search terms to find videos for. Opens all videos matching each term.
 
.PARAMETER Subscriptions
Opens all videos from subscribed channels.
 
.PARAMETER WatchLater
Opens all videos from the watch-later playlist.
 
.PARAMETER Recommended
Opens all videos that YouTube recommends.
 
.PARAMETER Trending
Opens all videos currently trending on YouTube.
 
.PARAMETER Edge
Use Microsoft Edge browser.
 
.PARAMETER Chrome
Use Google Chrome browser.
 
.EXAMPLE
Open-AllYoutubeVideos -Queries "PowerShell tutorial","vscode tips" -Edge
 
.EXAMPLE
qvideos PowerShell tutorial, vscode tips
#>

function Open-AllYoutubeVideos {

    [CmdletBinding()]
    [Alias("qvideos", "qyt")]

    param(
        ########################################################################
        [parameter(
            Mandatory = $false,
            Position = 0,
            ValueFromRemainingArguments = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = "YouTube search terms to find videos"
        )]
        [Alias("q", "Value", "Name", "Text", "Query")]
        [string[]] $Queries = @(""),
        ########################################################################

        [parameter(
            Mandatory = $false,
            HelpMessage = "Open videos from subscribed channels"
        )]
        [switch] $Subscriptions,
        ########################################################################

        [parameter(
            Mandatory = $false,
            HelpMessage = "Open videos from watch later playlist"
        )]
        [switch] $WatchLater,
        ########################################################################

        [parameter(
            Mandatory = $false,
            HelpMessage = "Open recommended videos"
        )]
        [switch] $Recommended,
        ########################################################################

        [parameter(
            Mandatory = $false,
            HelpMessage = "Open trending videos"
        )]
        [switch] $Trending,
        ########################################################################

        [parameter(
            Mandatory = $false,
            HelpMessage = "Use Microsoft Edge browser"
        )]
        [Alias("e")]
        [switch] $Edge,
        ########################################################################

        [parameter(
            Mandatory = $false,
            HelpMessage = "Use Google Chrome browser"
        )]
        [Alias("ch")]
        [switch] $Chrome
        ########################################################################
    )

    begin {

        $lastVideo = ""

        # get the main powershell window for positioning
        $powershellProcess = Get-PowershellMainWindowProcess
        $powershellWindow = [GenXdev.Helpers.WindowObj]::GetMainWindow(
            $powershellProcess)

        Write-Verbose "Starting YouTube video browser"
    }

    process {

        # fix casing consistency
        [bool] $currentTab = ($Recommended -ne $true) -and
            ($Subscriptions -ne $true) -and
            ($Trending -ne $true) -and
            ($WatchLater -ne $true) -and
            (($Queries.Count -eq 0) -or [String]::IsNullOrWhiteSpace($queries[0]))


        function go($Url = $null, $Query) {

            $Global:data = @{
                query          = $Query
                urls           = @()
                description    = ""
                title          = ""
                subscribeTitle = ""
                playing        = $true
                duration       = 0
                position       = 0
            }

            $AllScreens = @([WpfScreenHelper.Screen]::AllScreens | ForEach-Object { $PSItem });
            $hostInfo = & { $H = Get-Host; $H.ui.rawui; }
            $size = "$($hostInfo.WindowSize.Width)x$($hostInfo.WindowSize.Height)";
            Clear-Host
            Write-Host "Hold on.. launching query".PadRight($hostInfo.WindowSize.Width, " ") -BackgroundColor ([ConsoleColor]::Blue) -ForegroundColor ([ConsoleColor]::White)
            $browser = $null;

            if (-not $currentTab) {

                if ($PowershellWindow.Count -gt 0) {

                    $PowershellScreen = [WpfScreenHelper.Screen]::FromPoint(@{X = $PowershellWindow.Position().X; Y = $PowershellWindow.Position().Y });
                    $PowershellScreenIndex = $AllScreens.IndexOf($PowershellScreen) + 1;

                    [int] $defaultMonitor = 1;

                    if ([int]::TryParse($Global:DefaultSecondaryMonitor, [ref] $defaultMonitor)) {

                        $Monitor = $defaultMonitor % ($AllScreens.Length + 1);
                    }
                    else {

                        $Monitor = 2 % ($AllScreens.AllScreens.Length + 1);
                    }

                    if ($monitor -lt 1) {

                        if ($monitor -lt 0) {

                            $monitor = $PowershellWindow;
                        }
                        else {

                            $monitor = $AllScreens.IndexOf([WpfScreenHelper.Screen]::PrimaryScreen) + 1;
                        }
                    }

                    if ($PowershellScreenIndex -eq $Monitor) {

                        if ($PowershellScreen.WorkingArea.Width -gt $PowershellScreen.WorkingArea.Height) {

                            Set-WindowPosition -Left -Monitor $Monitor
                            $browser = Open-Webbrowser -NewWindow -RestoreFocus -Chromium -Edge:$Edge -Chrome:$Chrome -Right -Url $Url -PassThru -Force -NoBrowserExtensions
                            $null = Start-Sleep 1
                            $Global:chrome = $null
                        }
                        else {

                            Set-WindowPosition -Bottom -Monitor $Monitor
                            $browser = Open-Webbrowser -NewWindow -RestoreFocus -Chromium -Edge:$Edge -Chrome:$Chrome -Top -Url $Url -PassThru -Force -NoBrowserExtensions
                            $null = Start-Sleep 1
                            $Global:chrome = $null
                        }
                    }
                }

                if ($null -eq $browser) {

                    $browser = Open-Webbrowser -NewWindow -FullScreen -RestoreFocus -Chromium -Edge:$Edge -Chrome:$Chrome -Url $Url -PassThru -Force -NoBrowserExtensions
                    $null = Start-Sleep 1
                    $Global:chrome = $null

                }

                try {
                    $null = Select-WebbrowserTab -Name "*youtube*" -ErrorAction SilentlyContinue -Edge:$Edge -Chrome:$Chrome
                    $ChromeSessions | ForEach-Object { Select-WebbrowserTab -ByReference $_; Get-WebbrowserTabDomNodes "video" "e.pause()" }
                }
                catch {

                }

                $null = Select-WebbrowserTab -Name "*youtube*" -ErrorAction SilentlyContinue -Edge:$Edge -Chrome:$Chrome
            }
            else {

                $Global:chrome = $null
                $null = Select-WebbrowserTab -Name "*youtube*" -ErrorAction SilentlyContinue -Edge:$Edge -Chrome:$Chrome
            }

            $completed = $false;
            $job = [System.IO.File]::ReadAllText("$PSScriptRoot\..\..\Open-AllYoutubeVideos.js");
            [int] $scrollPosition = -1;
            [int] $scrollPosition2 = -1;

            $page = $Global:chromeController
            $reference = Get-ChromiumSessionReference

            # Add error handling for async operations
            function checkOpened() {

                $opened = $false

                try {
                    $i = 0
                    if ($null -ne $Global:data.open) {
                        $Global:data.open | ForEach-Object {
                            $i++
                            $opened = $true
                            $page = $page.Context.NewPageAsync().GetAwaiter().GetResult()
                            $null = $page.GoToAsync($PSItem).GetAwaiter().GetResult()
                            try {
                                $null = Select-WebbrowserTab -Name "*youtube*" -ErrorAction SilentlyContinue -Edge:$Edge -Chrome:$Chrome -Force
                                $reference = Get-ChromiumSessionReference
                                $page = $Global:chromeController
                                $null = Invoke-WebbrowserEvaluation "$job"
                                $null = Get-WebbrowserTabDomNodes "video" "e.pause()"
                            }
                            catch {
                                Write-Warning "Error in checkOpened: $_"
                            }
                        }
                    }
                }
                catch {

                    Write-Warning "Error in checkOpened: $_"
                }

                if ($opened) {

                    $Global:data.open = @();
                    # $null = Select-WebbrowserTab -Name "*youtube*" -ErrorAction SilentlyContinue -Edge:$Edge -Chrome:$Chrome -Force
                    # $null = Stop-WebbrowserVideos
                    # $page = $Global:chromeController
                    # $reference = Get-ChromiumSessionReference
                    $null = Set-ForegroundWindow ($powershellWindow.handle)
                }
            }

            while ((-not $completed) -and ($null -ne $page) -and ($null -ne $reference)) {

                try {
                    $hostInfo = & { $H = Get-Host; $H.ui.rawui; }
                    [Console]::SetCursorPosition(0, $hostInfo.WindowSize.Height - 2); [Console]::ResetColor();

                    $null = Invoke-WebbrowserEvaluation "$job;" -Page $Page -ByReference $reference;

                    checkOpened

                    if ($completed) { return; }

                    try {

                        $newsize = "$($hostInfo.WindowSize.Width)x$($hostInfo.WindowSize.Height)";
                        if ($newsize -ne $size) {

                            $size = $newsize;
                            $LastVideo = $null;
                        }
                        $sub = "";
                        $pause = " [P]ause | ";
                        $header = "[Q]uit | $sub$pause SPACE=Next | S = $(($data.subscribeTitle)) | F = Toggle fullscreen | [0]..[9] = skip | â—€ -20s | +20s â–¶ | ".PadRight($hostInfo.WindowSize.Width, " ");

                        if ($header.Length -gt $hostInfo.WindowSize.Width) {

                            $scrollPosition = ($scrollPosition + 1) % $header.length;
                            try {
                                $header = "$header $header".Substring($scrollPosition, $hostInfo.WindowSize.Width);
                            }
                            catch {
                                try {
                                    $header = "$header $header".Substring(0, $hostInfo.WindowSize.Width);
                                }
                                catch {
                                }
                            }
                        }

                        $scrollPosition = -1;
                        $scrollPosition2 = -1;
                        $videoInfo = "$($Global:data.title)$($Global:data.description)"

                        if ($videoInfo -ne $LastVideo) {
                            Clear-Host;
                            Write-Host $header -BackgroundColor ([ConsoleColor]::Blue) -ForegroundColor ([ConsoleColor]::White)
                            $header = "$($Global:data.title)".Replace("`r", "").Replace("`n", "`r").Replace("`t", " ").Trim().PadRight($hostInfo.WindowSize.Width, " ");
                            if ($header.Length -gt $hostInfo.WindowSize.Width) {

                                $scrollPosition2 = ($scrollPosition2 + 1) % $header.length;
                                try {
                                    $header = "$header $header".Substring($scrollPosition, $hostInfo.WindowSize.Width);
                                }
                                catch {
                                    try {
                                        $header = "$header $header".Substring(0, $hostInfo.WindowSize.Width);
                                    }
                                    catch {
                                    }
                                }
                            }

                            Write-Host $header -ForegroundColor ([ConsoleColor]::black) -BackgroundColor ([ConsoleColor]::Gray)
                            [int] $nn = 0;

                            if ($Global:data.description.Contains("\n1")) {

                                $Global:data.description = "loading.."
                            }

                            $txt = "$($Global:data.description)".Replace("Show less", "").Replace("Show more", "").Replace("`r", "").Replace("`n", "`r").Replace("`t", " ").Trim();
                            Write-Host ((($txt -Split "`r"  | ForEach-Object -ErrorAction SilentlyContinue {
                                            if ([string]::IsNullOrWhiteSpace($PSItem)) {
                                                $nn = $nn + 1;
                                            }
                                            else {
                                                $nn = 0
                                            }
                                            if ($nn -lt 2) {
                                                $s = $PSItem.Trim();
                                                for ([int] $i = $hostInfo.WindowSize.Width - 1; $i -lt $s.length - 1; $i += $hostInfo.WindowSize.Width - 3) {

                                                    $s = $s.substring(0, $i) + "`r" + $s.substring($i);
                                                }

                                                $s
                                            }
                                        }
                                    ) -Join "`r" -Split "`r" | Select-Object -First ($hostInfo.WindowSize.Height - 3)) -Join "`r`n")

                            $LastVideo = $videoInfo
                        }

                        [Console]::SetCursorPosition(0, $hostInfo.WindowSize.Height - 1);
                        [Console]::BackgroundColor = [ConsoleColor]::Blue;
                        [Console]::ForegroundColor = [ConsoleColor]::Yellow;
                        try { [Console]::Write([System.TimeSpan]::FromSeconds([Math]::Round($Global:data.Position, 0)).ToString()) } catch {}
                        [Console]::SetCursorPosition($hostInfo.WindowSize.Width - 9, $hostInfo.WindowSize.Height - 1);
                        try { [Console]::Write([System.TimeSpan]::FromSeconds([Math]::Round($Global:data.Duration - $Global:data.Position, 0)).ToString()) } catch {}
                        $hostInfo = & { $H = Get-Host; $H.ui.rawui; }
                        [Console]::SetCursorPosition(0, $hostInfo.WindowSize.Height - 2);
                        [Console]::ResetColor();

                        while ([Console]::KeyAvailable) {

                            [Console]::SetCursorPosition(0, $hostInfo.WindowSize.Height - 2);
                            $c = [Console]::ReadKey();

                            switch ("$($c.KeyChar)".ToLowerInvariant()) {

                                "q" {
                                    $completed = $true;
                                    [Console]::SetCursorPosition(0, 0);
                                    Write-Host "Quiting..".PadRight($hostInfo.WindowSize.Width, " ") -BackgroundColor ([ConsoleColor]::Blue) -ForegroundColor ([ConsoleColor]::Yellow)

                                    if ($null -ne $browser) {

                                        $browser.CloseMainWindow() | Out-Null
                                    }
                                    return;
                                }

                                " " {

                                    [Console]::SetCursorPosition(0, 0);
                                    Write-Host "Skipping to next video".PadRight($hostInfo.WindowSize.Width - 2, " ") -BackgroundColor ([ConsoleColor]::Blue) -ForegroundColor ([ConsoleColor]::Yellow)
                                    [Console]::SetCursorPosition(0, $hostInfo.WindowSize.Height - 2);
                                    $Page.CloseAsync().Wait()
                                    $null = Select-WebbrowserTab -Name "*youtube*" -ErrorAction SilentlyContinue -Edge:$Edge -Chrome:$Chrome
                                    $page = $Global:chromeController
                                    $reference = Get-ChromiumSessionReference
                                    $LastVideo = "";
                                    [Console]::SetCursorPosition(0, 0);
                                    Write-Host $header -BackgroundColor ([ConsoleColor]::Blue) -ForegroundColor ([ConsoleColor]::White)
                                    continue;
                                }

                                "s" {

                                    [Console]::SetCursorPosition(0, 0);
                                    Write-Host "Toggling subscription".PadRight($hostInfo.WindowSize.Width, " ") -BackgroundColor ([ConsoleColor]::Blue) -ForegroundColor ([ConsoleColor]::Yellow)
                                    $null = Invoke-WebbrowserEvaluation "$job;await toggleSubscribeToChannel();" -Page $Page -ByReference $reference | Out-Null
                                    $null = Start-Sleep 1
                                    checkOpened
                                    [Console]::SetCursorPosition(0, 0);
                                    Write-Host $header -BackgroundColor ([ConsoleColor]::Blue) -ForegroundColor ([ConsoleColor]::White)
                                }

                                "f" {

                                    [Console]::SetCursorPosition(0, 0);
                                    Write-Host "Toggling fullscreen".PadRight($hostInfo.WindowSize.Width, " ") -BackgroundColor ([ConsoleColor]::Blue) -ForegroundColor ([ConsoleColor]::Yellow)
                                    $null = Invoke-WebbrowserEvaluation "$job;await toggleFullscreenVideo();" -Page $Page -ByReference $reference | Out-Null
                                    $null = Start-Sleep 1
                                    checkOpened
                                    [Console]::SetCursorPosition(0, 0);
                                    Write-Host $header -BackgroundColor ([ConsoleColor]::Blue) -ForegroundColor ([ConsoleColor]::White)
                                }

                                "p" {

                                    [Console]::SetCursorPosition(0, 0);
                                    Write-Host "Toggling pause video".PadRight($hostInfo.WindowSize.Width, " ") -BackgroundColor ([ConsoleColor]::Blue) -ForegroundColor ([ConsoleColor]::Yellow)
                                    $LastVideo = "";
                                    $null = Invoke-WebbrowserEvaluation "$job;await togglePauseVideo();" -Page $Page -ByReference $reference | Out-Null
                                    $null = Start-Sleep 1
                                    checkOpened
                                    [Console]::SetCursorPosition(0, 0);
                                    Write-Host $header -BackgroundColor ([ConsoleColor]::Blue) -ForegroundColor ([ConsoleColor]::White)
                                }

                                default {

                                    [int] $n = 0;
                                    if ([int]::TryParse("$($c.KeyChar)", [ref] $n)) {

                                        [Console]::SetCursorPosition(0, 0);
                                        Write-Host "Skipping to position".PadRight($hostInfo.WindowSize.Width, " ") -BackgroundColor ([ConsoleColor]::Blue) -ForegroundColor ([ConsoleColor]::Yellow)
                                        $null = Invoke-WebbrowserEvaluation "$job;await setVideoPosition($n);" -Page $Page -ByReference $reference | Out-Null
                                        $null = Start-Sleep 1
                                        checkOpened
                                        [Console]::SetCursorPosition(0, 0);
                                        Write-Host $header -BackgroundColor ([ConsoleColor]::Blue) -ForegroundColor ([ConsoleColor]::White)
                                    }
                                    else {
                                        if ($c.Key -eq [ConsoleKey]::RightArrow) {

                                            [Console]::SetCursorPosition(0, 0);
                                            Write-Host "Skipping 20 seconds forward".PadRight($hostInfo.WindowSize.Width, " ") -BackgroundColor ([ConsoleColor]::Blue) -ForegroundColor ([ConsoleColor]::Yellow)
                                            $null = Invoke-WebbrowserEvaluation "$job;await forwardInVideo();" -Page $Page -ByReference $reference | Out-Null
                                            $null = Start-Sleep 1
                                            checkOpened
                                            [Console]::SetCursorPosition(0, 0);
                                            Write-Host $header -BackgroundColor ([ConsoleColor]::Blue) -ForegroundColor ([ConsoleColor]::White)
                                        }
                                        else {
                                            if ($c.Key -eq [ConsoleKey]::LeftArrow) {

                                                [Console]::SetCursorPosition(0, 0);
                                                Write-Host "Skipping 20 seconds backwards".PadRight($hostInfo.WindowSize.Width, " ") -BackgroundColor ([ConsoleColor]::Blue) -ForegroundColor ([ConsoleColor]::Yellow)
                                                $null = Invoke-WebbrowserEvaluation "$job;await backwardsInVideo();" -Page $Page -ByReference $reference | Out-Null
                                                $null = Start-Sleep 1
                                                checkOpened
                                                [Console]::SetCursorPosition(0, 0);
                                                Write-Host $header -BackgroundColor ([ConsoleColor]::Blue) -ForegroundColor ([ConsoleColor]::White)
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                    catch {

                        if ($LastVideo -ne "") {
                            $completed = $true;
                            return;
                        }
                    }
                    $hostInfo = & { $H = Get-Host; $H.ui.rawui; }
                    [Console]::SetCursorPosition(0, $hostInfo.WindowSize.Height - 2); [Console]::ResetColor();
                    $null = Select-WebbrowserTab -Name "*youtube*" -ErrorAction SilentlyContinue -Edge:$Edge -Chrome:$Chrome
                    $page = $Global:chromeController
                    $reference = Get-ChromiumSessionReference

                    if ((@($Global:chromeSessions).Count -eq 0) -or ($null -eq $Global:chrome)) {

                        throw "No active session"
                    }
                }
                catch {

                    $completed = $true;
                }
            }
        }

        $OldVerbosePreference = $VerbosePreference
        $VerbosePreference = 'SilentlyContinue'
        try {

            if ($currentTab) {

                go
                return;
            }

            if ($Queries.Count -gt 0) {

                foreach ($Query in $Queries) {

                    if ([string]::IsNullOrWhiteSpace($Query) -eq $false) {

                        go "https://www.youtube.com/results?search_query=$([Uri]::EscapeUriString($Query))" $Query
                    }
                }
            }

            if ($Subscriptions -eq $true) {

                go "https://www.youtube.com/feed/subscriptions"
            }

            if ($Recommended -eq $true) {

                go "https://www.youtube.com/"
            }

            if ($WatchLater -eq $true) {

                go "https://www.youtube.com/playlist?list=WL"
            }

            if ($Trending -eq $true) {

                go "https://www.youtube.com/feed/trending"
            }
        }
        finally {
            Clear-Host
            $VerbosePreference = $OldVerbosePreference
        }
    }

    end {
        Write-Verbose "YouTube video browser session ended"
    }
}