ocwd.ps1

<#PSScriptInfo
 
.VERSION 1.1.0
 
.GUID a31639e2-a8ab-4a29-9fed-66d5b5f9e1e9
 
.AUTHOR Aniruddha Mukherjee
 
.COMPANYNAME
 
.COPYRIGHT Aniruddha Mukherjee
 
.TAGS PSEdition_Core
 
.LICENSEURI https://www.gnu.org/licenses/gpl-3.0.en.html
 
.PROJECTURI https://github.com/amkhrjee/ocwd
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
 
 
.PRIVATEDATA
 
#>


<#
 
.DESCRIPTION
 Downloads MIT OCW course resources
 
#>
 
Param(
    [Parameter(HelpMessage = "The URL to a course homepage")]
    $link
)
function Show-Error($msg) {
    Write-Host $msg -ForegroundColor DarkRed
}
#shows the details of the course
function Get-Details {
    $titlePattern = '<title>(?<title>.*)</title>'
   
    if ($webResponse.Content -match $titlePattern) { 
        $titleName = $Matches.title -split "\|" #split expects a RE
        Write-Host "▶️Title:" $titleName[0]
        
    }
    else {
        Show-Error("Title Not Found")
    }
    # Don't touch this!
    $instructorPattern = '<a class="course-info-instructor strip-link-offline" href=".*">(?<instructor>.*)</a>'
   
    if ($webResponse.Content -match $instructorPattern) {
        Write-Host "▶️Instructor:" $Matches.instructor
    }
    else {
        Show-Error("Instructor Not Found")
    }

    $otherPattern = '<span class="course-number-term-detail">(?<other>.*)</span>'
    if ($webResponse.Content -match $otherPattern) { 
        $otherDetails = $Matches.other -split "\|"
        Write-Host "▶️ID:" $otherDetails[0].Trim()
        Write-Host "▶️Semester:" $otherDetails[1].Trim()
        Write-Host "▶️Level:" $otherDetails[2].Trim()
    }
}

function Set-ResourceList($downloadsPagelink) {
    try {
        $downloadPage = Invoke-WebRequest -Uri $downloadsPagelink
        $keys = 'Lecture Videos', 'Assignments', 'Exams', 'Lecture Notes'
        $resourceList = @()
        foreach ($key in $keys) {
            if ($downloadPage.Content -match $key) {
                $resourceList += $key
            }
        }
        return $resourceList
    }
    catch {
        Write-Host "Error in loading resouce list" -ForegroundColor Red
    }
}
function Show-Resources {
    # shows what resources are available for download
    Write-Host "::::::::::::::::::::::::::::::::"
    Write-Host "⚡Resources Available" -ForegroundColor Green
    Write-Host "::::::::::::::::::::::::::::::::"
    try {
        $index = 0
        foreach ($res in $resList) {
            Write-Host ($index += 1) $res 
        }
    }
    catch {
        Write-Error "Error in fetching resources"      
    }
}

function Get-Response() {
    Write-Host "Enter the index of the desired resource for download"  -ForegroundColor Cyan
    Write-Host "💡Use commas for multiple indices"  -ForegroundColor Gray
    Write-Host "💡Enter A for downloading all resources" -ForegroundColor Gray
    Write-Host "🪶 Example: 1,2"
    $userInputs = Read-Host "➡️"
    $target = Confirm-Response($userInputs)
    try {
        Write-Host "Enter the download path: " -ForegroundColor Cyan
        Write-Host "🪶Example: C:\Users\john\OneDrive\Desktop"
        $downloadPath = Read-Host "➡️"
    }
    catch {
        Show-Error("Invalid path")
    }
  
    Import-Resoruces $target $downloadPath
    Invoke-Item $downloadPath
}

function Confirm-Response($userInputs) {
    switch ($userInputs.Length) {
        0 {
            Show-Error("Input cannot be empty")
            Get-Response
        }
        1 {
            if (($userInputs -eq 'A') -or ($userInputs -eq 'a')) {
                return 999
            }
            else {
                if (($userInputs -lt 0) -or ($userInputs -gt $resList.Length)) {
                    Write-Host "Invalid Index" -ForegroundColor Red
                    Get-Response
                    break
                }
                return ($userInputs - 1)
            }
        }
        Default {
            $targetIndices = @()
            $userInputs = $userInputs -split ','
            foreach ($userInput in $userInputs) {
                if (($userInput -lt 0) -or ($userInput -gt $resList.Length)) {
                    Write-Host "Invalid Index" -ForegroundColor Red
                    Get-Response
                    break
                }
                $targetIndices += ($userInput - 1)
            }
            return $targetIndices
        }
    }
}

function Get-Files($baseUri, $dirName, $downloadPath) {
    $basePage = Invoke-WebRequest -Uri $baseUri
    # Thanks to Emanuel Palm -> "https://pipe.how/invoke-webscrape/#parsing-data"
    $AllMatches = ($basePage.Content | Select-String '<a href="(?<downloadLink>.*)" target="_blank" download>' -AllMatches).Matches
    $downloadLinksList = ($AllMatches.Groups.Where{ $_.Name -like 'downloadLink' }).Value
    
    # # File names: does not work because of unsupported naming convention of ocw
    # $titlePattern = '<a class="resource-list-title" href=".*">\n\s(?<resTitle>.*)\n\s+<\/a>'
    # $TitleMatches = ($basePage.Content | Select-String $titlePattern -AllMatches).Matches
    # $TitlesList = ($TitleMatches.Groups.Where{ $_.Name -like 'resTitle' }).Value
    $files = @()
    New-Item -Path ($downloadPath + $dirName) -ItemType Directory 
    $downloadPath = $downloadPath + $dirName
    $index = 1
    foreach ($downloadLink in $downloadLinksList) {
        if ($downloadLink -match '/courses/') {
            $downloadLink = 'https://ocw.mit.edu' + $downloadLink
            $extension = '.pdf'
        }
        else {
            $extension = '.mp4'
        }
        $files += @{
            Uri     = $downloadLink
            outFile = $downloadPath + '\' + ($dirName -split '\\')[1] + ' ' + ($index++) + $extension
        }
    }
    $jobs = @()
    foreach ($file in $files) {
        $jobs += Start-ThreadJob -Name $file.OutFile -ScriptBlock {
            $params = $using:file
            Invoke-WebRequest @params
        }
    }

    Write-Host ($dirName -split '\\')[1] "downloads in progress..." -ForegroundColor DarkYellow
    
    $waitJobLog = Wait-Job -Job $jobs

    $results = @()
    foreach ($job in $jobs) {
        $results += Receive-Job -Job $job
    }
    Write-Host  ($dirName -split '\\')[1] "downloads finished" -ForegroundColor Green
}
function Get-LVideos($downloadPath) {
    try {
        Get-Files ($link + 'resources/lecture-videos/') '\LVideos' $downloadPath
    }
    catch {
        Show-Error("Error in dowloading Lecture Videos")
    }
}
function Get-Assignments($downloadPath) {
    try {
        Get-Files ($link + 'resources/assignments/') '\Assignments' $downloadPath
    }
    catch {
        Show-Error("Error in downloading Assignments")
    }
}
function Get-Exams($downloadPath) {
    try {
        Get-Files ($link + 'resources/exams/') '\Exams' $downloadPath
    }
    catch {
        Show-Error("Error in downloading Exams")
    }
}
function Get-LNotes($downloadPath) {
    try {
        Get-Files ($link + 'resources/lecture-notes/') '\LNotes' $downloadPath
    }
    catch {
        Show-Error('Error in downloading LNotes')
    }
} 


function Import-Resoruces($target, $downloadPath) {
    if ($target -eq 999) {
        # download everything that is in the resList
        foreach ($res in $resList) {
            switch ($res) {
                'Lecture Videos' {
                    Write-Host "Downloading Lecture Videos..." -ForegroundColor Yellow
                    Get-LVideos($downloadPath)
                }
                'Assignments' {
                    Write-Host "Downloading Assignments..." -ForegroundColor Yellow
                    Get-Assignments($downloadPath)
                }
                'Exams' {
                    Write-Host "Downloading Exams..." -ForegroundColor Yellow
                    Get-Exams($downloadPath)
                }
                'Lecture Notes' {
                    Write-Host "Download Lecture Notes..." -ForegroundColor Yellow
                    Get-LNotes($downloadPath)
                }
            }
        }
    }
    else {
        foreach ($index in $target) {
            switch ($resList[$index]) {
                'Lecture Videos' {
                    Write-Host "Downloading Lecture Videos..." -ForegroundColor Yellow
                    Get-LVideos($downloadPath)
                }
                'Assignments' {
                    Write-Host "Downloading Assignments..." -ForegroundColor Yellow
                    Get-Assignments($downloadPath)
                }
                'Exams' {
                    Write-Host "Downloading Exams..." -ForegroundColor Yellow
                    Get-Exams($downloadPath)
                }
                'Lecture Notes' {
                    Write-Host "Downloadng Lecture Notes..." -ForegroundColor Yellow
                    Get-LNotes($downloadPath)
                }
            }
        }
    }
}

# driver
if ($link.Length -eq 0 ) {
    Write-Host "ocwd Copyright (C) 2022 Aniruddha Mukherjee"
    Write-Host "This program comes with ABSOLUTELY NO WARRANTY"
    Write-Host "This is free software, and you are welcome to redistribute it under certain conditions"
    Write-Host " "
    $link = Read-Host "Enter the URL to course homepage"
}
$link = $link.Trim()

if ($link -match 'https://ocw\.mit\.edu/courses') {
    try {   
        $webResponse = Invoke-WebRequest -Uri $link
        Write-Host "⚡Course Found" -ForegroundColor DarkGreen #make this more useful
        Write-Host ":::::::::::::::::::::::::::::::::::"
        Get-Details
        $downloadsPagelink = $link + 'download/'
        $resList = Set-ResourceList($downloadsPagelink)
        Show-Resources
        Get-Response
    }
    catch {
        $StatusCode = $_.Exception.Response.StatusCode.value__
        if ($StatusCode.Length -eq 0) { $StatusCode = 502 }
        $StatusCode = [System.Convert]::ToDecimal($StatusCode)
        Write-Host "Error: Exited with error code $StatusCode" -ForegroundColor Red
        if (($StatusCode -ge 300) -and ($StatusCode -lt 400)) {
            Write-Host "The URL provided does not exist" -ForegroundColor Red
        }
        elseif (($StatusCode -ge 400) -and ($StatusCode -lt 600)) {
            Write-Host "Please check your internet connection and try again" -ForegroundColor Red
        }
    }
 
}
else {
    Write-Host "Error: Invalid link" -ForegroundColor Red
    Write-Host "Please enter a link that starts with `"https://ocw.mit.edu/courses/"`" -ForegroundColor DarkRed
}