From e7605d7b3813753fd846f6aeeb1f041a044dec3a Mon Sep 17 00:00:00 2001 From: bryan cook <3217452+bryanbcook@users.noreply.github.com> Date: Wed, 23 Jul 2025 19:01:46 -0400 Subject: [PATCH 1/5] Get-BloggerPost without labels #24 --- src/public/Get-BloggerPost.ps1 | 13 ++++++++++--- src/tests/Get-BloggerPost.Tests.ps1 | 12 ++++++------ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/public/Get-BloggerPost.ps1 b/src/public/Get-BloggerPost.ps1 index b570367..9fdad7a 100644 --- a/src/public/Get-BloggerPost.ps1 +++ b/src/public/Get-BloggerPost.ps1 @@ -68,6 +68,7 @@ Function Get-BloggerPost { if ($null -eq $result) { throw "No post found with PostId '$PostId' in blog '$BlogId'." } + Write-Verbose "Post: $($result | ConvertTo-Json -Depth 10)" # Construct a subfolder based on the published date if ($FolderDateFormat -and $result.published) { @@ -80,12 +81,14 @@ Function Get-BloggerPost { # Ensure the output directory exists if (!(Test-Path -Path $OutDirectory)) { try { + Write-Verbose "Creating output directory: $OutDirectory" New-Item -ItemType Directory -Path $OutDirectory -Force | Out-Null } catch { throw "Failed to create output directory '$OutDirectory': $($_.Exception.Message)" } } + Write-Verbose "Using output directory: $OutDirectory" # Extract the HTML content $htmlContent = $result.content @@ -111,17 +114,21 @@ Function Get-BloggerPost { # Save the Post to a Markdown file "Markdown" { - + $title = $result.title $frontMatter = [ordered]@{ postId = $result.id } - if ($result['labels']) { + if ($result.PSObject.Properties.Name -contains "labels") { + Write-Verbose "Using post labels: $($result.labels)" $frontMatter['tags'] = $result.labels } else { + Write-Verbose "No labels found in post, using empty tags." $frontMatter['tags'] = @() } + Write-Verbose "Saving frontmatter: $($frontMatter | ConvertTo-Json -Depth 10)" $file = "$title.md" + $filePath = Join-Path -Path $OutDirectory -ChildPath $file ConvertTo-MarkdownFromHtml -Content $result.content -OutFile $filePath > $null Set-MarkdownFrontMatter -File $filePath -Replace $frontMatter @@ -140,7 +147,7 @@ Function Get-BloggerPost { return $result } catch { - throw "Failed to save post content to file '$filePath': $($_.Exception.Message)" + throw "Failed to save post content: $($_.Exception.Message)" } } catch { diff --git a/src/tests/Get-BloggerPost.Tests.ps1 b/src/tests/Get-BloggerPost.Tests.ps1 index 0d396fc..c16aacb 100644 --- a/src/tests/Get-BloggerPost.Tests.ps1 +++ b/src/tests/Get-BloggerPost.Tests.ps1 @@ -40,7 +40,7 @@ Describe "Get-BloggerPost" { # setup blog post retrieval Mock Invoke-GAPi { - return @{ content = "Test content" } + return [pscustomobject]@{ content = "Test content" } } } @@ -64,7 +64,7 @@ Describe "Get-BloggerPost" { It "Should call correct API endpoint" { InModuleScope PSBlogger { Mock Invoke-GApi { - return @{ content = "Test content" } + return [pscustomobject]@{ content = "Test content" } } -ParameterFilter { $uri -eq "https://www.googleapis.com/blogger/v3/blogs/test-blog-id/posts/123" } -Verifiable @@ -179,7 +179,7 @@ Describe "Get-BloggerPost" { # mock post retrieval Mock Invoke-GApi { - return @{ + return [pscustomobject]@{ id = $postId title = "Test Post" published = [datetime]"2023-10-01T17:30:00-04:00" @@ -232,7 +232,7 @@ Describe "Get-BloggerPost" { # mock post retrieval Mock Invoke-GApi { - return @{ + return [pscustomobject]@{ id = $postId title = "Test Post" published = [datetime]"2023-10-01T17:30:00-04:00" @@ -260,7 +260,7 @@ Describe "Get-BloggerPost" { # mock post retrieval Mock Invoke-GApi { - return @{ + return [pscustomobject]@{ id = $postId title = "Test Post" published = [datetime]"2023-10-01T17:30:00-04:00" @@ -306,7 +306,7 @@ Describe "Get-BloggerPost" { # mock post retrieval Mock Invoke-GApi { - return @{ + return [pscustomobject]@{ id = $postId title = "Test Post" published = [datetime]"2023-10-01T17:30:00-04:00" From f76f377ece852fc9389bc434e484f3e63b9411cc Mon Sep 17 00:00:00 2001 From: bryan cook <3217452+bryanbcook@users.noreply.github.com> Date: Thu, 24 Jul 2025 10:49:28 -0400 Subject: [PATCH 2/5] corrected indentation of files --- src/public/Add-GoogleDriveFile.ps1 | 286 ++++++++++--------- src/public/Add-GoogleDriveFolder.ps1 | 54 ++-- src/public/ConvertTo-HtmlFromMarkdown.ps1 | 92 +++--- src/public/ConvertTo-MarkdownFromHtml.ps1 | 8 +- src/public/Find-MarkdownImages.ps1 | 183 ++++++------ src/public/Get-GoogleDriveItems.ps1 | 120 ++++---- src/public/Initialize-Blogger.ps1 | 74 +++-- src/public/Publish-BloggerPost.ps1 | 142 +++++---- src/public/Publish-MarkdownDriveImages.ps1 | 14 +- src/public/Set-BloggerConfig.ps1 | 18 ++ src/public/Set-GoogleDriveFilePermission.ps1 | 51 ++-- src/public/Set-MarkdownFrontMatter.ps1 | 22 ++ src/public/Update-MarkdownImages.ps1 | 112 ++++---- 13 files changed, 625 insertions(+), 551 deletions(-) diff --git a/src/public/Add-GoogleDriveFile.ps1 b/src/public/Add-GoogleDriveFile.ps1 index 1a877ad..66730ca 100644 --- a/src/public/Add-GoogleDriveFile.ps1 +++ b/src/public/Add-GoogleDriveFile.ps1 @@ -1,135 +1,136 @@ <# .SYNOPSIS - Uploads a file to Google Drive in a specified folder. + Uploads a file to Google Drive in a specified folder. .DESCRIPTION - Uploads a file to Google Drive, places it in the "Open Live Writer" subfolder, - preserves the original filename. + Uploads a file to Google Drive, places it in the "Open Live Writer" subfolder, + preserves the original filename. .PARAMETER FilePath - The local path to the file to upload. + The local path to the file to upload. .PARAMETER FileName - Optional custom name for the file. If not specified, uses the original filename. + Optional custom name for the file. If not specified, uses the original filename. .PARAMETER Force - If specified, will overwrite an existing file with the same name in the target folder. - If not specified and the file already exists, it will return the existing file's metadata. + If specified, will overwrite an existing file with the same name in the target folder. + If not specified and the file already exists, it will return the existing file's metadata. .EXAMPLE - Add-GoogleDriveFile -FilePath "C:\images\photo.jpg" + Add-GoogleDriveFile -FilePath "C:\images\photo.jpg" #> function Add-GoogleDriveFile { - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)] - [ValidateScript({ Test-Path -Path $_ -PathType Leaf })] - [string]$FilePath, + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [ValidateScript({ Test-Path -Path $_ -PathType Leaf })] + [string]$FilePath, - [Parameter()] - [string]$FileName, + [Parameter()] + [string]$FileName, - [Parameter()] - [string]$TargetFolderName = "Open Live Writer", + [Parameter()] + [string]$TargetFolderName = "Open Live Writer", - [Parameter()] - [switch]$Force - ) + [Parameter()] + [switch]$Force + ) - $sourceItem = Get-Item (Resolve-Path $FilePath) - Write-Verbose "Add-GoogleDriveFile: Uploading file: $($sourceItem.FullName) to Google Drive" + $sourceItem = Get-Item (Resolve-Path $FilePath) + Write-Verbose "Add-GoogleDriveFile: Uploading file: $($sourceItem.FullName) to Google Drive" - if (-not $FileName) { - $FileName = $sourceItem.Name + if (-not $FileName) { + $FileName = $sourceItem.Name + } + + # First, find or create the upload folder in Google Drive + Write-Verbose "Add-GoogleDriveFile: Verifying target folder: $TargetFolderName" + $folder = Get-GoogleDriveItems -ResultType "Folders" -Title $TargetFolderName + + if (-not $folder) { + # Create the folder if it doesn't exist + Write-Verbose "Add-GoogleDriveFile: Folder '$TargetFolderName' not found. Creating new folder." + $folder = Add-GoogleDriveFolder -Name $TargetFolderName + } + else { + # Get the first folder if multiple exist + Write-Verbose "Add-GoogleDriveFile: Folder '$TargetFolderName' found." + $folder = $folder | Select-Object -First 1 + } + + # Determine if the file already exists in the target folder + Write-Verbose "Add-GoogleDriveFile: Checking if file '$FileName' already exists in folder '$TargetFolderName'" + $existingFile = Get-GoogleDriveItems -ResultType "Files" -Title $FileName -ParentId $folder.id + if ($existingFile) { + if (-not $Force) { + # use existing file + return New-GoogleDriveMetadata -id $existingFile.id -name $existingFile.name } + # address multiple file issue + # todo: evaluate additional meta-data of the file to ensure it's not deleted + $existingFile = $existingFile | Select-Object -First 1 + } - # First, find or create the upload folder in Google Drive - Write-Verbose "Add-GoogleDriveFile: Verifying target folder: $TargetFolderName" - $folder = Get-GoogleDriveItems -ResultType "Folders" -Title $TargetFolderName - - if (-not $folder) { - # Create the folder if it doesn't exist - Write-Verbose "Add-GoogleDriveFile: Folder '$TargetFolderName' not found. Creating new folder." - $folder = Add-GoogleDriveFolder -Name $TargetFolderName - } else { - # Get the first folder if multiple exist - Write-Verbose "Add-GoogleDriveFile: Folder '$TargetFolderName' found." - $folder = $folder | Select-Object -First 1 - } - - # Determine if the file already exists in the target folder - Write-Verbose "Add-GoogleDriveFile: Checking if file '$FileName' already exists in folder '$TargetFolderName'" - $existingFile = Get-GoogleDriveItems -ResultType "Files" -Title $FileName -ParentId $folder.id + $sourceMime = Get-ImageMimeType -Extension $sourceItem.Extension + + # Prepare metadata for the file + $metadata = @{ + name = $FileName + parents = @($folder.id) + } | ConvertTo-Json -Compress + + $fileContent = [System.IO.File]::ReadAllBytes($sourceItem.FullName) + $fileContentBase64 = [Convert]::ToBase64String($fileContent) + + # Create multipart body + $boundary = "boundary_" + [System.Guid]::NewGuid().ToString() + + $body = @( + + # Metadata part + "--$boundary" + "Content-Type: application/json; charset=UTF-8" + "" + $metadata + "--$boundary" + + # File content part + "Content-Type: $sourceMime" + "Content-Transfer-Encoding: base64" + "" + $fileContentBase64 + "--$boundary--" + ) -join "`r`n" + + $additionalHeaders = @{ + "Content-Type" = "multipart/related; boundary=$boundary" + } + + try { + if ($existingFile) { - if (-not $Force) { - # use existing file - return New-GoogleDriveMetadata -id $existingFile.id -name $existingFile.name - } - # address multiple file issue - # todo: evaluate additional meta-data of the file to ensure it's not deleted - $existingFile = $existingFile | Select-Object -First 1 - } + # If the file exists and Force is specified, update it + $uri = "https://www.googleapis.com/upload/drive/v3/files/$($existingFile.id)?uploadType=media" + $method = "PATCH" - $sourceMime = Get-ImageMimeType -Extension $sourceItem.Extension - - # Prepare metadata for the file - $metadata = @{ - name = $FileName - parents = @($folder.id) - } | ConvertTo-Json -Compress - - $fileContent = [System.IO.File]::ReadAllBytes($sourceItem.FullName) - $fileContentBase64 = [Convert]::ToBase64String($fileContent) - - # Create multipart body - $boundary = "boundary_" + [System.Guid]::NewGuid().ToString() - - $body = @( - - # Metadata part - "--$boundary" - "Content-Type: application/json; charset=UTF-8" - "" - $metadata - "--$boundary" - - # File content part - "Content-Type: $sourceMime" - "Content-Transfer-Encoding: base64" - "" - $fileContentBase64 - "--$boundary--" - ) -join "`r`n" - - $additionalHeaders = @{ - "Content-Type" = "multipart/related; boundary=$boundary" - } + "Add-GoogleDriveFile: $Method $uri" | Write-Verbose - try { - - if ($existingFile) { - # If the file exists and Force is specified, update it - $uri = "https://www.googleapis.com/upload/drive/v3/files/$($existingFile.id)?uploadType=media" - $method = "PATCH" - - "Add-GoogleDriveFile: $Method $uri" | Write-Verbose - - $uploadResult = Invoke-GApi -uri $uri -InFile $sourceItem.FullName -method $method -ContentType $sourceMime -Verbose:$false - } - else { - $uri = "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart" - $method = "POST" - "Add-GoogleDriveFile: $Method $uri" | Write-Verbose - - $uploadResult = Invoke-GApi -uri $uri -body $body -method $method -ContentType "multipart/related; boundary=$boundary" -AdditionalHeaders $additionalHeaders -Verbose:$false - } - - # Return the file information with public URL - return New-GoogleDriveMetadata -id $uploadResult.id -name $uploadResult.name + $uploadResult = Invoke-GApi -uri $uri -InFile $sourceItem.FullName -method $method -ContentType $sourceMime -Verbose:$false } - catch { - Write-Error "Failed to upload file to Google Drive: $($_.Exception.Message). $($_.ErrorDetails | ConvertTo-Json -Depth 10)" -ErrorAction Stop + else { + $uri = "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart" + $method = "POST" + "Add-GoogleDriveFile: $Method $uri" | Write-Verbose + + $uploadResult = Invoke-GApi -uri $uri -body $body -method $method -ContentType "multipart/related; boundary=$boundary" -AdditionalHeaders $additionalHeaders -Verbose:$false } + + # Return the file information with public URL + return New-GoogleDriveMetadata -id $uploadResult.id -name $uploadResult.name + } + catch { + Write-Error "Failed to upload file to Google Drive: $($_.Exception.Message). $($_.ErrorDetails | ConvertTo-Json -Depth 10)" -ErrorAction Stop + } } <# @@ -146,45 +147,46 @@ The file extension (including the dot). Get-ImageMimeType -Extension ".jpg" #> function Get-ImageMimeType { - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)] - [string]$Extension - ) - - $mimeTypes = @{ - '.jpg' = 'image/jpeg' - '.jpeg' = 'image/jpeg' - '.png' = 'image/png' - '.gif' = 'image/gif' - '.bmp' = 'image/bmp' - '.webp' = 'image/webp' - '.svg' = 'image/svg+xml' - '.ico' = 'image/x-icon' - '.tiff' = 'image/tiff' - '.tif' = 'image/tiff' - } - - $normalizedExtension = $Extension.ToLower() + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Extension + ) + + $mimeTypes = @{ + '.jpg' = 'image/jpeg' + '.jpeg' = 'image/jpeg' + '.png' = 'image/png' + '.gif' = 'image/gif' + '.bmp' = 'image/bmp' + '.webp' = 'image/webp' + '.svg' = 'image/svg+xml' + '.ico' = 'image/x-icon' + '.tiff' = 'image/tiff' + '.tif' = 'image/tiff' + } + + $normalizedExtension = $Extension.ToLower() - if ($mimeTypes.ContainsKey($normalizedExtension)) { - return $mimeTypes[$normalizedExtension] - } else { - return 'application/octet-stream' - } + if ($mimeTypes.ContainsKey($normalizedExtension)) { + return $mimeTypes[$normalizedExtension] + } + else { + return 'application/octet-stream' + } } function New-GoogleDriveMetadata { - param( - [string]$id, - [string]$name - ) - $publicUrl = "https://lh3.googleusercontent.com/d/$id" + param( + [string]$id, + [string]$name + ) + $publicUrl = "https://lh3.googleusercontent.com/d/$id" - return [PSCustomObject]@{ - Id = $id - Name = $name - PublicUrl = $publicUrl - DriveUrl = "https://drive.google.com/file/d/$id/view" - } + return [PSCustomObject]@{ + Id = $id + Name = $name + PublicUrl = $publicUrl + DriveUrl = "https://drive.google.com/file/d/$id/view" + } } diff --git a/src/public/Add-GoogleDriveFolder.ps1 b/src/public/Add-GoogleDriveFolder.ps1 index 398b7f2..d501bb8 100644 --- a/src/public/Add-GoogleDriveFolder.ps1 +++ b/src/public/Add-GoogleDriveFolder.ps1 @@ -3,45 +3,45 @@ Creates a new folder in Google Drive. .DESCRIPTION - Creates a new folder in Google Drive with the specified name. + Creates a new folder in Google Drive with the specified name. .PARAMETER Name - The name of the folder to create. + The name of the folder to create. .PARAMETER ParentId - Optional parent folder ID. If not specified, creates in root. + Optional parent folder ID. If not specified, creates in root. .EXAMPLE - New-GoogleDriveFolder -Name "Open Live Writer" + New-GoogleDriveFolder -Name "Open Live Writer" #> function Add-GoogleDriveFolder { - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)] - [string]$Name, + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Name, - [Parameter(Mandatory=$false)] - [string]$ParentId - ) + [Parameter(Mandatory = $false)] + [string]$ParentId + ) - Write-Verbose ("Creating folder '$Name' {0}" -f ($ParentId ? "in parent '$ParentId'" : "in root")) + Write-Verbose ("Creating folder '$Name' {0}" -f ($ParentId ? "in parent '$ParentId'" : "in root")) - $metadata = @{ - name = $Name - mimeType = "application/vnd.google-apps.folder" - } + $metadata = @{ + name = $Name + mimeType = "application/vnd.google-apps.folder" + } - if ($ParentId) { - $metadata.parents = @($ParentId) - } + if ($ParentId) { + $metadata.parents = @($ParentId) + } - $body = $metadata | ConvertTo-Json -Compress - $uri = "https://www.googleapis.com/drive/v3/files" + $body = $metadata | ConvertTo-Json -Compress + $uri = "https://www.googleapis.com/drive/v3/files" - try { - return Invoke-GApi -uri $uri -body $body -Verbose:$false - } - catch { - Write-Error "Failed to create folder in Google Drive: $($_.Exception.Message)" -ErrorAction Stop - } + try { + return Invoke-GApi -uri $uri -body $body -Verbose:$false + } + catch { + Write-Error "Failed to create folder in Google Drive: $($_.Exception.Message)" -ErrorAction Stop + } } diff --git a/src/public/ConvertTo-HtmlFromMarkdown.ps1 b/src/public/ConvertTo-HtmlFromMarkdown.ps1 index 46da4fe..c0a9623 100644 --- a/src/public/ConvertTo-HtmlFromMarkdown.ps1 +++ b/src/public/ConvertTo-HtmlFromMarkdown.ps1 @@ -1,68 +1,66 @@ <# .SYNOPSIS - Convert a Markdown file to HTML using Pandoc + Convert a Markdown file to HTML using Pandoc .PARAMETER File - The file path of the markdown file + The file path of the markdown file .PARAMETER OutFile - The resulting html. If this parameter is not specified an HTML file with the same name of the markdown file will be created. + The resulting html. If this parameter is not specified an HTML file with the same name of the markdown file will be created. #> -function ConvertTo-HtmlFromMarkdown -{ - param( - [Parameter(Mandatory=$true, HelpMessage="Path to Markdown file")] - [ValidateScript({ Test-Path $_ -PathType Leaf})] - [string]$File, +function ConvertTo-HtmlFromMarkdown { + param( + [Parameter(Mandatory = $true, HelpMessage = "Path to Markdown file")] + [ValidateScript({ Test-Path $_ -PathType Leaf })] + [string]$File, - [Parameter(HelpMessage="File path to create")] - #[ValidateScript({ Test-Path $_ -Include "*.html" -PathType Container})] - [string]$OutFile - ) + [Parameter(HelpMessage = "File path to create")] + #[ValidateScript({ Test-Path $_ -Include "*.html" -PathType Container})] + [string]$OutFile + ) - # ensure that the file is an absolute path because pandoc.exe doesn't like powershell relative paths - $File = (Resolve-Path $File).Path + # ensure that the file is an absolute path because pandoc.exe doesn't like powershell relative paths + $File = (Resolve-Path $File).Path - # Use pandoc to convert the markdown to Html - $pandocArgs = "`"{0}`" " -f $File - $pandocArgs += "-f {0} " -f $BloggerSession.PandocMarkdownFormat - $pandocArgs += "-t {0} " -f $BloggerSession.PandocHtmlFormat + # Use pandoc to convert the markdown to Html + $pandocArgs = "`"{0}`" " -f $File + $pandocArgs += "-f {0} " -f $BloggerSession.PandocMarkdownFormat + $pandocArgs += "-t {0} " -f $BloggerSession.PandocHtmlFormat - # add template and toc if template is available - if (Test-Path $BloggerSession.PandocTemplate) { - Write-Verbose "Using template" - $pandocArgs += "--template `"{0}`" --toc " -f $BloggerSession.PandocTemplate - } - # add additional command-line arguments - if ($BloggerSession.PandocAdditionalArgs) { - Write-Verbose "Using additional args" - $pandocArgs += "{0} " -f $BloggerSession.PandocAdditionalArgs - } + # add template and toc if template is available + if (Test-Path $BloggerSession.PandocTemplate) { + Write-Verbose "Using template" + $pandocArgs += "--template `"{0}`" --toc " -f $BloggerSession.PandocTemplate + } + # add additional command-line arguments + if ($BloggerSession.PandocAdditionalArgs) { + Write-Verbose "Using additional args" + $pandocArgs += "{0} " -f $BloggerSession.PandocAdditionalArgs + } - if (!($OutFile)) - { - $OutFile = Join-Path (Split-Path $File -Parent) ((Split-Path $File -LeafBase) + ".html") - Write-Verbose "Using OutFile: $OutFile" - } + if (!($OutFile)) { + $OutFile = Join-Path (Split-Path $File -Parent) ((Split-Path $File -LeafBase) + ".html") + Write-Verbose "Using OutFile: $OutFile" + } - $pandocArgs += "-o `"{0}`" " -f $OutFile + $pandocArgs += "-o `"{0}`" " -f $OutFile - Write-Verbose ">> pandoc $($pandocArgs)" - Start-Process pandoc -ArgumentList $pandocArgs -NoNewWindow -Wait + Write-Verbose ">> pandoc $($pandocArgs)" + Start-Process pandoc -ArgumentList $pandocArgs -NoNewWindow -Wait - # Apply additional transforms - $content = Get-Content $OutFile -Raw + # Apply additional transforms + $content = Get-Content $OutFile -Raw - ## TEMP: My blog doesn't support
 yet, so I'm trimming it out.
-    # convert 
 -> 
-    $content = $content -replace '',''
-    # convert 
->
- $content = $content -replace '
','' + ## TEMP: My blog doesn't support
 yet, so I'm trimming it out.
+  # convert 
 -> 
+  $content = $content -replace '', ''
+  # convert 
->
+ $content = $content -replace '
', '' - Set-Content -Path $OutFile -Value $content + Set-Content -Path $OutFile -Value $content - Remove-Item $OutFile + Remove-Item $OutFile - return $content + return $content } \ No newline at end of file diff --git a/src/public/ConvertTo-MarkdownFromHtml.ps1 b/src/public/ConvertTo-MarkdownFromHtml.ps1 index a350b44..0d4b431 100644 --- a/src/public/ConvertTo-MarkdownFromHtml.ps1 +++ b/src/public/ConvertTo-MarkdownFromHtml.ps1 @@ -1,15 +1,15 @@ <# .SYNOPSIS - Convert HTML content or a HTML file to Markdown using Pandoc + Convert HTML content or a HTML file to Markdown using Pandoc .PARAMETER File - The file path of the html file. Required when Content is not specified. + The file path of the html file. Required when Content is not specified. .PARAMETER Content - The HTML content to convert to Markdown. Required when File is not specified. + The HTML content to convert to Markdown. Required when File is not specified. .PARAMETER OutFile - The resulting markdown file, if specified. + The resulting markdown file, if specified. #> function ConvertTo-MarkdownFromHtml { diff --git a/src/public/Find-MarkdownImages.ps1 b/src/public/Find-MarkdownImages.ps1 index 29176ae..d4e4f8f 100644 --- a/src/public/Find-MarkdownImages.ps1 +++ b/src/public/Find-MarkdownImages.ps1 @@ -15,114 +15,115 @@ Find-MarkdownImages -File "post.md" #> function Find-MarkdownImages { - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)] - [ValidateScript({ Test-Path -Path $_ -PathType Leaf })] - [string]$File - ) + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [ValidateScript({ Test-Path -Path $_ -PathType Leaf })] + [string]$File + ) - $content = Get-Content -Path $File -Raw - $images = @() - $fileDirectory = Split-Path -Path $File -Parent + $content = Get-Content -Path $File -Raw + $images = @() + $fileDirectory = Split-Path -Path $File -Parent - # If the file is in the current directory, use the current directory - if ([string]::IsNullOrEmpty($fileDirectory)) { - $fileDirectory = "." - } + # If the file is in the current directory, use the current directory + if ([string]::IsNullOrEmpty($fileDirectory)) { + $fileDirectory = "." + } - # Regex pattern for standard markdown images: ![alt text](image_path "optional title") - $standardPattern = '!\[([^\]]*)\]\(([^)]+?)(?:\s+"([^"]*)")?\)' + # Regex pattern for standard markdown images: ![alt text](image_path "optional title") + $standardPattern = '!\[([^\]]*)\]\(([^)]+?)(?:\s+"([^"]*)")?\)' - # Regex pattern for Obsidian images: ![[image_path|alt text]] - $obsidianPattern = '!\[\[([^|\]]+?)(?:\|([^\]]*))?\]\]' + # Regex pattern for Obsidian images: ![[image_path|alt text]] + $obsidianPattern = '!\[\[([^|\]]+?)(?:\|([^\]]*))?\]\]' - # Collect all matches with their positions - $allMatches = @() + # Collect all matches with their positions + $allMatches = @() - # Find all standard markdown image matches - $standardMatches = [regex]::Matches($content, $standardPattern) - foreach ($match in $standardMatches) { - $allMatches += @{ - Match = $match - Position = $match.Index - Format = "Standard" - } + # Find all standard markdown image matches + $standardMatches = [regex]::Matches($content, $standardPattern) + foreach ($match in $standardMatches) { + $allMatches += @{ + Match = $match + Position = $match.Index + Format = "Standard" } + } - # Find all Obsidian image matches - $obsidianMatches = [regex]::Matches($content, $obsidianPattern) - foreach ($match in $obsidianMatches) { - $allMatches += @{ - Match = $match - Position = $match.Index - Format = "Obsidian" - } + # Find all Obsidian image matches + $obsidianMatches = [regex]::Matches($content, $obsidianPattern) + foreach ($match in $obsidianMatches) { + $allMatches += @{ + Match = $match + Position = $match.Index + Format = "Obsidian" } + } - # Sort matches by position in document - $allMatches = $allMatches | Sort-Object Position + # Sort matches by position in document + $allMatches = $allMatches | Sort-Object Position - # Process each match in document order - foreach ($matchInfo in $allMatches) { - $match = $matchInfo.Match - $format = $matchInfo.Format + # Process each match in document order + foreach ($matchInfo in $allMatches) { + $match = $matchInfo.Match + $format = $matchInfo.Format - if ($format -eq "Standard") { - $altText = $match.Groups[1].Value - $imagePath = $match.Groups[2].Value.Trim() - $title = if ($match.Groups[3].Success) { $match.Groups[3].Value } else { "" } - } else { - # Obsidian format - $imagePath = $match.Groups[1].Value.Trim() - $altText = if ($match.Groups[2].Success) { $match.Groups[2].Value } else { "" } - $title = "" # Obsidian format doesn't support titles - } + if ($format -eq "Standard") { + $altText = $match.Groups[1].Value + $imagePath = $match.Groups[2].Value.Trim() + $title = if ($match.Groups[3].Success) { $match.Groups[3].Value } else { "" } + } + else { + # Obsidian format + $imagePath = $match.Groups[1].Value.Trim() + $altText = if ($match.Groups[2].Success) { $match.Groups[2].Value } else { "" } + $title = "" # Obsidian format doesn't support titles + } - # Skip URLs (images already hosted online) - if ($imagePath -match '^https?://') { - continue - } + # Skip URLs (images already hosted online) + if ($imagePath -match '^https?://') { + continue + } - # Resolve relative paths - if (-not [System.IO.Path]::IsPathRooted($imagePath)) { - $resolvedPath = Join-Path -Path $fileDirectory -ChildPath $imagePath - } else { - $resolvedPath = $imagePath - } + # Resolve relative paths + if (-not [System.IO.Path]::IsPathRooted($imagePath)) { + $resolvedPath = Join-Path -Path $fileDirectory -ChildPath $imagePath + } + else { + $resolvedPath = $imagePath + } - # Check if the file exists - if (Test-Path -Path $resolvedPath -PathType Leaf) { - $images += New-MarkdownImage ` - -OriginalMarkdown $match.Value ` - -AltText $altText ` - -LocalPath $resolvedPath ` - -RelativePath $imagePath ` - -Title $title ` - -FileName $(Split-Path -Path $resolvedPath -Leaf) - } + # Check if the file exists + if (Test-Path -Path $resolvedPath -PathType Leaf) { + $images += New-MarkdownImage ` + -OriginalMarkdown $match.Value ` + -AltText $altText ` + -LocalPath $resolvedPath ` + -RelativePath $imagePath ` + -Title $title ` + -FileName $(Split-Path -Path $resolvedPath -Leaf) } + } - return $images + return $images } -Function New-MarkdownImage -{ - param( - [string]$OriginalMarkdown, - [string]$AltText, - [string]$LocalPath, - [string]$RelativePath, - [string]$Title = "", - [string]$FileName - ) - return [PSCustomObject]@{ - OriginalMarkdown = $OriginalMarkdown - AltText = $AltText - LocalPath = $LocalPath - RelativePath = $RelativePath - Title = $Title - FileName = $FileName - NewUrl = $null # This will be set after uploading to Google Drive - } +Function New-MarkdownImage { + param( + [string]$OriginalMarkdown, + [string]$AltText, + [string]$LocalPath, + [string]$RelativePath, + [string]$Title = "", + [string]$FileName + ) + return [PSCustomObject]@{ + OriginalMarkdown = $OriginalMarkdown + AltText = $AltText + LocalPath = $LocalPath + RelativePath = $RelativePath + Title = $Title + FileName = $FileName + NewUrl = $null # This will be set after uploading to Google Drive + } } \ No newline at end of file diff --git a/src/public/Get-GoogleDriveItems.ps1 b/src/public/Get-GoogleDriveItems.ps1 index a056d44..9ee5e20 100644 --- a/src/public/Get-GoogleDriveItems.ps1 +++ b/src/public/Get-GoogleDriveItems.ps1 @@ -1,85 +1,85 @@ <# .DESCRIPTION - Queries files and folders in the Google drive associated with the authenticated account + Queries files and folders in the Google drive associated with the authenticated account .PARAMETER ResultType - Optional filter to specify the type of results to return. Valid values are: - - All: Returns all files and folders - - Files: Returns only files - - Folders: Returns only folders + Optional filter to specify the type of results to return. Valid values are: + - All: Returns all files and folders + - Files: Returns only files + - Folders: Returns only folders - Default is All. + Default is All. .PARAMETER Title - Optional filter to return files or folders with a specific title. + Optional filter to return files or folders with a specific title. .PARAMETER ParentId - Optional filter to return files or folders that are children of a specific parent folder. - If not specified, returns items from the root directory. + Optional filter to return files or folders that are children of a specific parent folder. + If not specified, returns items from the root directory. #> -function Get-GoogleDriveItems -{ - [CmdletBinding()] - param( - [Parameter()] - [ValidateSet("All","Files","Folders")] - [string]$ResultType = "All", - - [Parameter()] - [string]$Title, - - [Parameter()] - [string]$ParentId - ) - - $q = @() - - # mimeType - if ($ResultType -ne "All") { - if ($ResultType -eq "Folders") { - $q += "mimeType='application/vnd.google-apps.folder'" - } - else { - $q += "mimeType!='application/vnd.google-apps.folder'" - } +function Get-GoogleDriveItems { + [CmdletBinding()] + param( + [Parameter()] + [ValidateSet("All", "Files", "Folders")] + [string]$ResultType = "All", + + [Parameter()] + [string]$Title, + + [Parameter()] + [string]$ParentId + ) + + $q = @() + + # mimeType + if ($ResultType -ne "All") { + if ($ResultType -eq "Folders") { + $q += "mimeType='application/vnd.google-apps.folder'" } - - # title - if (![string]::IsNullOrEmpty($Title)) { - $q += "name='$Title'" + else { + $q += "mimeType!='application/vnd.google-apps.folder'" } + } - # parents - if (![string]::IsNullOrEmpty($ParentId)) { - $q += "'$ParentId' in parents" - } + # title + if (![string]::IsNullOrEmpty($Title)) { + $q += "name='$Title'" + } + + # parents + if (![string]::IsNullOrEmpty($ParentId)) { + $q += "'$ParentId' in parents" + } - $queryArgs = @{ - q = [System.Web.HttpUtility]::UrlEncode($q -join ' and ') - pageSize=40 - } + $queryArgs = @{ + q = [System.Web.HttpUtility]::UrlEncode($q -join ' and ') + pageSize = 40 + } - do { + do { - $queryString = $queryArgs.GetEnumerator() | ForEach-Object { "$($_.Name)=$($_.Value)"} | Join-String -Separator "&" + $queryString = $queryArgs.GetEnumerator() | ForEach-Object { "$($_.Name)=$($_.Value)" } | Join-String -Separator "&" - $uri = "https://www.googleapis.com/drive/v3/files?$queryString" + $uri = "https://www.googleapis.com/drive/v3/files?$queryString" - "Get-GoogleDriveItems: $uri" | Write-Verbose + "Get-GoogleDriveItems: $uri" | Write-Verbose - $result = Invoke-GApi -uri $uri + $result = Invoke-GApi -uri $uri - # stream results - $result.files + # stream results + $result.files - if ('nextPageToken' -in $result.PSObject.Properties.Name) { - $queryArgs.pageToken = $result.nextPageToken - } else { - $queryArgs.pageToken = $null - } + if ('nextPageToken' -in $result.PSObject.Properties.Name) { + $queryArgs.pageToken = $result.nextPageToken + } + else { + $queryArgs.pageToken = $null + } - $result | Out-String | Write-Verbose + $result | Out-String | Write-Verbose - } while($queryArgs.pageToken) + } while ($queryArgs.pageToken) } \ No newline at end of file diff --git a/src/public/Initialize-Blogger.ps1 b/src/public/Initialize-Blogger.ps1 index 82729ed..ad41697 100644 --- a/src/public/Initialize-Blogger.ps1 +++ b/src/public/Initialize-Blogger.ps1 @@ -1,70 +1,68 @@ <# .SYNOPSIS - Initialize the local system to use Pandoc + Blogger together + Initialize the local system to use Pandoc + Blogger together .DESCRIPTION - This prepares your system to use Pandoc + Blogger together by obtaining an authtoken that is - authorized to communicate with blogger. + This prepares your system to use Pandoc + Blogger together by obtaining an authtoken that is + authorized to communicate with blogger. .PARAMETER clientId - Google API Client ID. This currently defaults to the one I use, but you - will need to create your own until the Google Application is published and verified + Google API Client ID. This currently defaults to the one I use, but you + will need to create your own until the Google Application is published and verified - .PARAMETER clientSecret - Google API Client Secret. A default value is provided, but you can provide your own if you don't trust me. + Google API Client Secret. A default value is provided, but you can provide your own if you don't trust me. .PARAMETER redirectUri - The oAuth redirect URL specifed in the Google API Consent Form. + The oAuth redirect URL specifed in the Google API Consent Form. .EXAMPLE Initiate a login flow with Google - Initialize-Blogger + Initialize-Blogger #> -Function Initialize-Blogger -{ - Param( - [Parameter(HelpMessage="Google API ClientId")] - [string]$clientId = "284606892422-ribvo7oodlbtd70e8onn8rg4hm58mluj.apps.googleusercontent.com", +Function Initialize-Blogger { + Param( + [Parameter(HelpMessage = "Google API ClientId")] + [string]$clientId = "284606892422-ribvo7oodlbtd70e8onn8rg4hm58mluj.apps.googleusercontent.com", - [Parameter(HelpMessage="Google API Client Secret")] - [string]$clientSecret = "PUK0j9ig-GHcSByQao2i1aIa", + [Parameter(HelpMessage = "Google API Client Secret")] + [string]$clientSecret = "PUK0j9ig-GHcSByQao2i1aIa", - [Parameter(HelpMessage="Redirect Uri specified in Google API Consent Form")] - [string]$redirectUri = "http://localhost/oauth2callback" - ) + [Parameter(HelpMessage = "Redirect Uri specified in Google API Consent Form")] + [string]$redirectUri = "http://localhost/oauth2callback" + ) - $ErrorActionPreference = 'Stop' + $ErrorActionPreference = 'Stop' - Write-Information "Let's get an auth-code." + Write-Information "Let's get an auth-code." - # specify the scopes we want in our auth token - $scope = @( - "https://www.googleapis.com/auth/blogger" - #"https://www.googleapis.com/auth/drive.file" - "https://www.googleapis.com/auth/drive" + # specify the scopes we want in our auth token + $scope = @( + "https://www.googleapis.com/auth/blogger" + #"https://www.googleapis.com/auth/drive.file" # TODO: fix + "https://www.googleapis.com/auth/drive" - ) -join " " + ) -join " " - $url = "https://accounts.google.com/o/oauth2/auth?client_id=$clientId&scope=$scope&response_type=code&redirect_uri=$redirectUri&access_type=offline&approval_prompt=force" + $url = "https://accounts.google.com/o/oauth2/auth?client_id=$clientId&scope=$scope&response_type=code&redirect_uri=$redirectUri&access_type=offline&approval_prompt=force" - Start-Process $url + Start-Process $url - $code = Wait-GoogleAuthApiToken + $code = Wait-GoogleAuthApiToken - Write-Information "Sucessfully obtained auth-code!" + Write-Information "Sucessfully obtained auth-code!" - # trade the auth-code for an token that has a short-lived expiry - $expiringToken = Get-GoogleAccessToken -clientId $clientId -clientSecret $clientSecret -redirectUri $redirectUri -code $code + # trade the auth-code for an token that has a short-lived expiry + $expiringToken = Get-GoogleAccessToken -clientId $clientId -clientSecret $clientSecret -redirectUri $redirectUri -code $code - # use the refresh-token to get an updated access-token - $token = Update-GoogleAccessToken -clientId $clientId -clientSecret $clientSecret -refreshToken $expiringToken.refresh_token + # use the refresh-token to get an updated access-token + $token = Update-GoogleAccessToken -clientId $clientId -clientSecret $clientSecret -refreshToken $expiringToken.refresh_token - # save the token in the credential_cache.json - Set-CredentialCache -clientId $clientId -clientSecret $clientSecret -refreshToken $expiringToken -token $token + # save the token in the credential_cache.json + Set-CredentialCache -clientId $clientId -clientSecret $clientSecret -refreshToken $expiringToken -token $token - Write-Information "Awesome. We're all set." + Write-Information "Awesome. We're all set." } \ No newline at end of file diff --git a/src/public/Publish-BloggerPost.ps1 b/src/public/Publish-BloggerPost.ps1 index 0a0de15..1a37dc6 100644 --- a/src/public/Publish-BloggerPost.ps1 +++ b/src/public/Publish-BloggerPost.ps1 @@ -1,65 +1,91 @@ -Function Publish-BloggerPost -{ - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)] - [string]$BlogId, - - [Parameter()] - [string]$PostId, - - [Parameter(Mandatory=$true)] - [string]$Title, - - [Parameter(Mandatory=$true)] - [string]$Content, - - [string[]]$Labels, - - [switch]$Draft - ) - - $uri = "https://www.googleapis.com/blogger/v3/blogs/$BlogId/posts" - $method = "POST" - - # if the postId exists, we're performing an update - if ($PostId) - { - $uri += "/$PostId" - $method = "PUT" - - if (-not $Draft) { - $uri += "?publish=true" - } - - } else { - if ($Draft) { - $uri += "?isDraft=true" - } - } - - $body = @{ - kind= "blogger#post" - blog = @{ - id = $BlogId - } - title = $Title - content = $Content - labels = $Labels +<# +.SYNOPSIS + Publish a blog post to Blogger. + +.DESCRIPTION + Publish a blog post to blogger as a final or draft post + +.PARAMETER BlogId + Required. The Id of the blog to publish the post to. + +.PARAMETER PostId + Optional. The Id of the post to update. If not specified, a new post will be created. + +.PARAMETER Title + Required. The title of the post. + +.PARAMETER Content + Required. The content of the post in HTML format. + +.PARAMETER Labels + Optional. An array of labels (tags) to apply to the post. + +.PARAMETER Draft + Optional. If specified, the post will be saved as a draft instead of being published. + +#> +Function Publish-BloggerPost { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$BlogId, + + [Parameter()] + [string]$PostId, + + [Parameter(Mandatory = $true)] + [string]$Title, + + [Parameter(Mandatory = $true)] + [string]$Content, + + [string[]]$Labels, + + [switch]$Draft + ) + + $uri = "https://www.googleapis.com/blogger/v3/blogs/$BlogId/posts" + $method = "POST" + + # if the postId exists, we're performing an update + if ($PostId) { + $uri += "/$PostId" + $method = "PUT" + + if (-not $Draft) { + $uri += "?publish=true" + } + + } + else { + if ($Draft) { + $uri += "?isDraft=true" + } + } + + $body = @{ + kind = "blogger#post" + blog = @{ + id = $BlogId } + title = $Title + content = $Content + labels = $Labels + } - $body | ConvertTo-Json | Write-Verbose + $body | ConvertTo-Json | Write-Verbose - $post = Invoke-GApi -Uri $uri -Body ($body | ConvertTo-Json) -Method $method + $post = Invoke-GApi -Uri $uri -Body ($body | ConvertTo-Json) -Method $method - $postUrl = ` - if ($Draft) { - "https://www.blogger.com/blog/post/edit/preview/$BlogId/$($post.id)" - } else { - $post.url - } + $postUrl = ` + if ($Draft) { + "https://www.blogger.com/blog/post/edit/preview/$BlogId/$($post.id)" + } + else { + $post.url + } - Start-Process $postUrl + Start-Process $postUrl - return $post + return $post } \ No newline at end of file diff --git a/src/public/Publish-MarkdownDriveImages.ps1 b/src/public/Publish-MarkdownDriveImages.ps1 index 2a7dba5..27589aa 100644 --- a/src/public/Publish-MarkdownDriveImages.ps1 +++ b/src/public/Publish-MarkdownDriveImages.ps1 @@ -1,22 +1,22 @@ <# .SYNOPSIS - Publishes local images from a markdown file to Google Drive and updates the markdown file with the new URLs. + Publishes local images from a markdown file to Google Drive and updates the markdown file with the new URLs. .DESCRIPTION - This function finds all local images referenced in a markdown file, uploads them to Google Drive, - sets public permissions, and updates the markdown file with the new Google Drive URLs. + This function finds all local images referenced in a markdown file, uploads them to Google Drive, + sets public permissions, and updates the markdown file with the new Google Drive URLs. .PARAMETER File - The path to the markdown file containing image references. + The path to the markdown file containing image references. .PARAMETER Force - If specified, will overwrite existing files in Google Drive with the same name. + If specified, will overwrite existing files in Google Drive with the same name. .EXAMPLE - Publish-MarkdownDriveImages -File "blog-post.md" + Publish-MarkdownDriveImages -File "blog-post.md" .EXAMPLE - Publish-MarkdownDriveImages -File "blog-post.md" -Force + Publish-MarkdownDriveImages -File "blog-post.md" -Force #> Function Publish-MarkdownDriveImages { diff --git a/src/public/Set-BloggerConfig.ps1 b/src/public/Set-BloggerConfig.ps1 index d4e9619..8355602 100644 --- a/src/public/Set-BloggerConfig.ps1 +++ b/src/public/Set-BloggerConfig.ps1 @@ -1,3 +1,21 @@ +<# +.SYNOPSIS + Updates the local user preferences + +.DESCRIPTION + Updates local user preferences that are used in Cmdlets in this module. + +.PARAMETER Name + Name of the user preferene to set. Valid values are: + - BlogId: The ID of the Blogger blog to use. + - PandocAdditionalArgs: Additional arguments to pass to Pandoc when converting Markdown to HTML. + - PandocHtmlFormat: The HTML format to use when converting Markdown to HTML. + - PandocMarkdownFormat: The Markdown format to use when converting HTML to Markdown. + - ExcludeLabels: Labels to exclude when publishing to Blogger. + +.PARAMETER Value + The value to set for the specified user preference. Specify an empty string to remove the preference. +#> Function Set-BloggerConfig { [CmdletBinding()] diff --git a/src/public/Set-GoogleDriveFilePermission.ps1 b/src/public/Set-GoogleDriveFilePermission.ps1 index e0797cd..8ecf353 100644 --- a/src/public/Set-GoogleDriveFilePermission.ps1 +++ b/src/public/Set-GoogleDriveFilePermission.ps1 @@ -1,41 +1,40 @@ <# .SYNOPSIS -Sets public read permissions on a Google Drive file. + Sets public read permissions on a Google Drive file. .DESCRIPTION -Configures a Google Drive file to be publicly accessible without authentication. + Configures a Google Drive file to be publicly accessible without authentication. .PARAMETER FileId -The ID of the Google Drive file to make public. + The ID of the Google Drive file to make public. .EXAMPLE -Set-GoogleDriveFilePermission -FileId "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms" + Set-GoogleDriveFilePermission -FileId "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms" #> -# Change to Set-GoogleDriveFile Permission function Set-GoogleDriveFilePermission { - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)] - [string]$FileId, + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$FileId, - [Parameter(Mandatory=$true)] - [PSCustomObject]$PermissionData - ) + [Parameter(Mandatory = $true)] + [PSCustomObject]$PermissionData + ) - # $permissionData = @{ - # role = "reader" - # type = "anyone" - # } | ConvertTo-Json - $body = $PermissionData | ConvertTo-Json -Compress + # $permissionData = @{ + # role = "reader" + # type = "anyone" + # } | ConvertTo-Json + $body = $PermissionData | ConvertTo-Json -Compress - $uri = "https://www.googleapis.com/drive/v3/files/$FileId/permissions" + $uri = "https://www.googleapis.com/drive/v3/files/$FileId/permissions" - try { - $result = Invoke-GApi -uri $uri -body $body - Write-Verbose "Set-GoogleDriveFilePermission: Set public permissions for file ID: $FileId" - return $result - } - catch { - Write-Error "Failed to set public permissions on Google Drive file: $($_.Exception.Message)$([Environment]::NewLine)$($_.ErrorDetails | ConvertTo-Json -Depth 10)" -ErrorAction Stop - } + try { + $result = Invoke-GApi -uri $uri -body $body + Write-Verbose "Set-GoogleDriveFilePermission: Set public permissions for file ID: $FileId" + return $result + } + catch { + Write-Error "Failed to set public permissions on Google Drive file: $($_.Exception.Message)$([Environment]::NewLine)$($_.ErrorDetails | ConvertTo-Json -Depth 10)" -ErrorAction Stop + } } diff --git a/src/public/Set-MarkdownFrontMatter.ps1 b/src/public/Set-MarkdownFrontMatter.ps1 index 6511748..188373f 100644 --- a/src/public/Set-MarkdownFrontMatter.ps1 +++ b/src/public/Set-MarkdownFrontMatter.ps1 @@ -1,3 +1,25 @@ +<# +.SYNOPSIS + Updates or Replaces the FrontMatter in a Markdown document + +.DESCRIPTION + Updates or Replaces the FrontMatter in a Markdown document with the specified values. + +.PARAMETER File + The path to the Markdown file to update. + +.PARAMETER Update + A hashtable of values to update in the front matter. If a key does not exist, it will be added. + +.PARAMETER Replace + An ordered dictionary to replace the entire front matter. This will overwrite any existing front matter. + +.EXAMPLE + Set-MarkdownFrontMatter -File "post.md" -Update @{title="New Title"; postid="12345"} + +.EXAMPLE + Set-MarkdownFrontMatter -File "post.md" -Replace @{title="New Title"; postid="12345"; date="2023-10-01"} +#> Function Set-MarkdownFrontMatter { [CmdletBinding(SupportsShouldProcess=$true)] diff --git a/src/public/Update-MarkdownImages.ps1 b/src/public/Update-MarkdownImages.ps1 index 2ced776..88ff720 100644 --- a/src/public/Update-MarkdownImages.ps1 +++ b/src/public/Update-MarkdownImages.ps1 @@ -1,74 +1,84 @@ <# .SYNOPSIS -Updates markdown content by replacing local image references with Google Drive URLs. + Updates markdown content by replacing local image references with Google Drive URLs. .DESCRIPTION -Takes a markdown file and replaces local image references with Google Drive public URLs, -converting all images to standard markdown format while preserving alt text and titles. + Takes a markdown file and replaces local image references with Google Drive public URLs, + converting all images to standard markdown format while preserving alt text and titles. .PARAMETER File -The path to the markdown file to update. + The path to the markdown file to update. + +.PARAMETER OutFile + Optional. If specified, writes the updated markdown content to this file instead of overwriting the original file. .PARAMETER ImageMappings -An array of objects containing the mapping between original markdown and new URLs. -Each object should have OriginalMarkdown and NewUrl properties. + An array of objects containing the mapping between original markdown and new URLs. + Each object should have OriginalMarkdown and NewUrl properties. .EXAMPLE -$mappings = @( + $mappings = @( @{ OriginalMarkdown = "![alt](local.jpg)"; NewUrl = "https://drive.google.com/uc?export=view&id=123"; AltText = "alt"; Title = "" } @{ OriginalMarkdown = "![[image.png|description]]"; NewUrl = "https://drive.google.com/uc?export=view&id=456"; AltText = "description"; Title = "" } -) -Update-MarkdownImages -File "post.md" -ImageMappings $mappings + ) + Update-MarkdownImages -File "post.md" -ImageMappings $mappings + +.EXAMPLE + $mappings = Find-MarkdownImages -File "post.md" + $mappings[0].NewUrl = "https://drive.google.com/uc?export=view&id=123" + Update-MarkdownImages -File "post.md" -ImageMappings $mappings -OutFile "updated-post.md" #> function Update-MarkdownImages { - [CmdletBinding()] - param( - [Parameter(Mandatory=$true)] - [ValidateScript({ Test-Path -Path $_ -PathType Leaf })] - [string]$File, + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [ValidateScript({ Test-Path -Path $_ -PathType Leaf })] + [string]$File, - [Parameter(Mandatory=$true)] - [array]$ImageMappings, + [Parameter(Mandatory = $true)] + [array]$ImageMappings, - [Parameter()] - [string]$OutFile - ) - $TargetFile = $File + [Parameter()] + [string]$OutFile + ) + $TargetFile = $File - Write-Verbose "UpdateMarkdownImages: Processing file $TargetFile with $($ImageMappings.Count) image mappings" - $content = Get-Content -Path $TargetFile -Raw - $originalContent = $content + Write-Verbose "UpdateMarkdownImages: Processing file $TargetFile with $($ImageMappings.Count) image mappings" + $content = Get-Content -Path $TargetFile -Raw + $originalContent = $content - foreach ($mapping in $ImageMappings) { - $originalMarkdown = $mapping.OriginalMarkdown - $newUrl = $mapping.NewUrl - $altText = $mapping.AltText - $title = $mapping.Title - - # Construct the new markdown image syntax - if ($title) { - $newMarkdown = "![$altText]($newUrl `"$title`")" - } else { - $newMarkdown = "![$altText]($newUrl)" - } + foreach ($mapping in $ImageMappings) { + $originalMarkdown = $mapping.OriginalMarkdown + $newUrl = $mapping.NewUrl + $altText = $mapping.AltText + $title = $mapping.Title - # Replace the original markdown with the new one - $content = $content -replace [regex]::Escape($originalMarkdown), $newMarkdown + # Construct the new markdown image syntax + if ($title) { + $newMarkdown = "![$altText]($newUrl `"$title`")" } - - # If OutFile is specified, write the updated content to that file - if ($OutFile) { - Write-Verbose "UpdateMarkdownImages: Changing output file from $File to $OutFile" - $TargetFile = $OutFile + else { + $newMarkdown = "![$altText]($newUrl)" } + + # Replace the original markdown with the new one + $content = $content -replace [regex]::Escape($originalMarkdown), $newMarkdown + } - # Only write the file if content has changed - if ($content -ne $originalContent) { - Set-Content -Path $TargetFile -Value $content -NoNewline - Write-Verbose "Updated markdown file: $TargetFile" - return $true - } else { - Write-Verbose "No changes made to markdown file: $File" - return $false - } + # If OutFile is specified, write the updated content to that file + if ($OutFile) { + Write-Verbose "UpdateMarkdownImages: Changing output file from $File to $OutFile" + $TargetFile = $OutFile + } + + # Only write the file if content has changed + if ($content -ne $originalContent) { + Set-Content -Path $TargetFile -Value $content -NoNewline + Write-Verbose "Updated markdown file: $TargetFile" + return $true + } + else { + Write-Verbose "No changes made to markdown file: $File" + return $false + } } From 936423b52efdd15d8f1817ff2b93b2186d11f658 Mon Sep 17 00:00:00 2001 From: bryan cook <3217452+bryanbcook@users.noreply.github.com> Date: Thu, 24 Jul 2025 10:53:37 -0400 Subject: [PATCH 3/5] indentation --- src/public/Get-BloggerBlogs.ps1 | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/public/Get-BloggerBlogs.ps1 b/src/public/Get-BloggerBlogs.ps1 index 9171dd1..d4603a7 100644 --- a/src/public/Get-BloggerBlogs.ps1 +++ b/src/public/Get-BloggerBlogs.ps1 @@ -3,21 +3,20 @@ Gets a list of Blogger Blogs associated to the user #> -Function Get-BloggerBlogs -{ - [CmdletBinding()] - param( +Function Get-BloggerBlogs { + [CmdletBinding()] + param( - ) + ) - try { - $uri = "https://www.googleapis.com/blogger/v3/users/self/blogs" + try { + $uri = "https://www.googleapis.com/blogger/v3/users/self/blogs" - $result = Invoke-GApi -uri $uri + $result = Invoke-GApi -uri $uri - $result.items - } - catch { - Write-Error $_.ToString() -ErrorAction Stop - } + $result.items + } + catch { + Write-Error $_.ToString() -ErrorAction Stop + } } \ No newline at end of file From c2eef0a08b0ec1eb2d228c2fe379e4f23a238b9d Mon Sep 17 00:00:00 2001 From: bryan cook <3217452+bryanbcook@users.noreply.github.com> Date: Thu, 24 Jul 2025 10:55:04 -0400 Subject: [PATCH 4/5] remove deprecated script --- src/login.ps1 | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 src/login.ps1 diff --git a/src/login.ps1 b/src/login.ps1 deleted file mode 100644 index a3768bd..0000000 --- a/src/login.ps1 +++ /dev/null @@ -1,6 +0,0 @@ -Param( - [string]$code -) -$credential = Get-Content .\credentials.json | ConvertFrom-Json - -Initialize-Blogger -$code \ No newline at end of file From b61691e6cf148e9c0524243762f05c89c49bdd26 Mon Sep 17 00:00:00 2001 From: bryan cook <3217452+bryanbcook@users.noreply.github.com> Date: Thu, 24 Jul 2025 11:13:03 -0400 Subject: [PATCH 5/5] Check if nextPageToken exists #23 --- src/public/Get-BloggerPosts.ps1 | 10 ++++++++-- src/tests/Get-BloggerPosts.Tests.ps1 | 11 +++++------ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/public/Get-BloggerPosts.ps1 b/src/public/Get-BloggerPosts.ps1 index 1d2e88e..10b1b5c 100644 --- a/src/public/Get-BloggerPosts.ps1 +++ b/src/public/Get-BloggerPosts.ps1 @@ -55,8 +55,14 @@ Function Get-BloggerPosts { $result.items - # loop if pageToken is present and -All switch is set - $pageToken = $result.nextPageToken + # fetch continuation token + if ($result.PSObject.Properties.name -contains "nextPageToken") { + $pageToken = $result.nextPageToken + } + else { + $pageToken = $null + } + # loop if pageToken is present and -All switch is set $done = ($All.IsPresent -and $All -and [string]::IsNullOrEmpty($pageToken)) -or !$All.IsPresent } } diff --git a/src/tests/Get-BloggerPosts.Tests.ps1 b/src/tests/Get-BloggerPosts.Tests.ps1 index 6d913f5..606a6b7 100644 --- a/src/tests/Get-BloggerPosts.Tests.ps1 +++ b/src/tests/Get-BloggerPosts.Tests.ps1 @@ -12,7 +12,7 @@ Describe "Get-BloggerPosts" { # Setup initial get-bloggerposts Mock Invoke-GApi { - return @{ + return [pscustomobject]@{ items = @( @{ id = "1"; title = "Post 1"; published = "2023-10-01T00:00:00Z" }, @{ id = "2"; title = "Post 2"; published = "2023-10-02T00:00:00Z" } @@ -24,11 +24,10 @@ Describe "Get-BloggerPosts" { # setup second call Mock Invoke-GApi { - @{ + return [pscustomobject]@{ items = @( @{ id = "3"; title = "Post 3"; published = "2023-10-03T00:00:00Z" } ) - nextPageToken = $null } } -ParameterFilter { $uri -like "*pageToken*" } } @@ -48,7 +47,7 @@ Describe "Get-BloggerPosts" { InModuleScope PSBlogger { # Setup initial get-bloggerposts Mock Invoke-GApi { - return @{ + return [pscustomobject]@{ items = @( @{ id = "1"; title = "Post 1"; published = "2023-10-01T00:00:00Z" }, @{ id = "2"; title = "Post 2"; published = "2023-10-02T00:00:00Z" } @@ -80,7 +79,7 @@ Describe "Get-BloggerPosts" { # Mock the API call to return posts after a specific date Mock Invoke-GApi { - return @{ + return [pscustomobject]@{ items = @( @{ id = "1"; title = "Post 1"; published = "2023-10-01T00:00:00Z" }, @{ id = "2"; title = "Post 2"; published = "2023-10-02T00:00:00Z" } @@ -105,7 +104,7 @@ Describe "Get-BloggerPosts" { # Mock the API call to return all posts Mock Invoke-GApi { - return @{ + return [pscustomobject]@{ items = @( @{ id = "1"; title = "Post 1"; published = "2023-10-01T00:00:00Z" }, @{ id = "2"; title = "Post 2"; published = "2023-10-02T00:00:00Z" }