From 6c100c11a32b3400fa1617a297829e130b79391f Mon Sep 17 00:00:00 2001 From: cecibaldoni Date: Tue, 2 Sep 2025 12:17:34 +0200 Subject: [PATCH 1/4] add initial scripts for blog-aitable automation --- scripts/blog-events.R | 94 +++++++++++++++++++++++++++++++++++++++++ scripts/blog-proposal.R | 70 ++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 scripts/blog-events.R create mode 100644 scripts/blog-proposal.R diff --git a/scripts/blog-events.R b/scripts/blog-events.R new file mode 100644 index 0000000000..cdb1d67263 --- /dev/null +++ b/scripts/blog-events.R @@ -0,0 +1,94 @@ +library(airtabler) +library(tidyverse) +library(glue) +library(readr) + +# Setup ---- +api_key <- Sys.setenv("AIRTABLE_API_KEY") +base_id <- Sys.getenv("AIRTABLE_BASE_ID") +table_name <- Sys.getenv("AIRTABLE_TABLE_ID") + + +# Connect to base ---- +base <- airtable(base = base_id, + tables = c("Proposals", "Event Reports")) + +# Get data ---- +events_data <- base$`Event Reports`$select() +str(events_data) + +events_to_draft <- events_data %>% + filter(`Status` == "In progress", Decision == "Accept") + +# @Mo, I tried and failed all morning to make a Status column in the event report like the one in proposals. +# I guess it could also have a manual toggle +# From here it assumes there is a "Status" column) + +if (nrow(events_to_draft) == 0) { + print("No new event reports to draft") + quit() +} + +print(paste("Found", nrow(events_to_draft), "new event report(s) to draft.")) + +for (i in 1:nrow(events_to_draft)) { + + report <- events_to_draft[i, ] + + slug <- report$title %>% + str_to_lower() %>% + str_replace_all("[^a-z0-9\\s-]", "") %>% + str_replace_all("\\s+", "-") + + # I use the *submission date* (`createdTime`) for the post date, + # as the event `date` will always be in the past. + post_date <- ymd_hms(report$createdTime) + year <- year(post_date) + month_day_slug <- paste0(format(post_date, "%m-%d"), "-", slug) + + file_path <- file.path("content", "blog", year, month_day_slug) + + dir.create(file_path, recursive = TRUE, showWarnings = FALSE) + + # Markdown front-matter ---- + front_matter <- glue( + '--- + title: "Event Report: {report$title}" + author: "{report$author}" + date: "{format(post_date, "%Y-%m-%d")}" + tags: [{report$keywords}] + --- + + ' + ) + + # standardised blogpost + post_body <- glue( + 'On {report$date}, {report$chapter} hosted the event "{report$title}". + The session was led by {report$speakers} and was attended by {report$participants} participants. + + ## Event Summary + + Here are the key topics that were covered: + + {report$summary} + + ## Attendee Feedback + + > {report$`Quotes or Reactions (optional)`} + + ## Resources + + A big thank you to the speakers and everyone who attended!' + ) + + final_content <- paste0(front_matter, post_body) + final_file_name <- file.path(file_path, "index.en.md") + + writeLines(final_content, final_file_name) + + print(paste("Successfully created event report file:", final_file_name)) + + # next: logic to update Airtable +} + diff --git a/scripts/blog-proposal.R b/scripts/blog-proposal.R new file mode 100644 index 0000000000..b7142d9bda --- /dev/null +++ b/scripts/blog-proposal.R @@ -0,0 +1,70 @@ +library(airtabler) +library(tidyverse) +library(glue) +library(readr) + +# Setup ---- +api_key <- Sys.setenv("AIRTABLE_API_KEY") +base_id <- Sys.getenv("AIRTABLE_BASE_ID") +table_name <- Sys.getenv("AIRTABLE_TABLE_ID") + +# Connect to base ---- +base <- airtable(base = base_id, + tables = c("Proposals", "Event Reports")) + +# Get data ---- +proposals_data <- base$Proposals$select() +events_data <- base$`Event Reports`$select() + +str(proposals_data) +str(events_data) + +proposals_to_draft <- proposals_data %>% + filter(`Status` == "In progress", Decision == "Accept") + +if (nrow(proposals_to_draft) == 0) { + print("No new proposals to draft.") + quit() +} + +print(paste("Found", nrow(proposals_to_draft), "new proposal(s) to draft.")) + +# Loop through the proposals and create md file ---- +for (i in 1:nrow(proposals_to_draft)) { + proposal <- proposals_to_draft + proposal <- proposals_to_draft[i, ] + + # "slug" from the title --- + slug <- proposal$Title %>% + str_to_lower() %>% + str_replace_all("[^a-z0-9\\s-]", "") %>% + str_replace_all("\\s+", "-") + + post_date_obj <- ymd(proposal$`Post date`) + month_day <- format(post_date_obj, "%m-%d") + year <- year(post_date_obj) + final_name_part <- paste0(month_day, "_", slug) + + file_path <- file.path("content", "blog", year, final_name_part) + dir.create(file_path, recursive = TRUE, showWarnings = FALSE) # @Mo, recursive=TRUE to keep? + + # metadata for md file + front_matter <- glue::glue( + '--- + title: "{proposal$Title}" + author: "{proposal$Author}" + date: "{proposal$`Post date`}" + --- + + ' + ) + body_content <- proposal$Description + blog_file <- file.path(file_path, "index.en.md") + + writeLines(paste0(front_matter, body_content), blog_file) + + print(paste("Successfully created draft file:", blog_file)) + + # Next, logic to update Airtable? + +} From 781c9505499b13d054c2365b9af705b414f1c294 Mon Sep 17 00:00:00 2001 From: cecibaldoni Date: Sat, 4 Oct 2025 12:36:21 +0200 Subject: [PATCH 2/4] refactor scripts based on code review feedback --- scripts/blog-events.R | 94 ----------------------------------- scripts/blog-proposal.R | 70 -------------------------- scripts/blog_events.R | 103 +++++++++++++++++++++++++++++++++++++++ scripts/blog_functions.R | 58 ++++++++++++++++++++++ scripts/blog_proposal.R | 74 ++++++++++++++++++++++++++++ 5 files changed, 235 insertions(+), 164 deletions(-) delete mode 100644 scripts/blog-events.R delete mode 100644 scripts/blog-proposal.R create mode 100644 scripts/blog_events.R create mode 100644 scripts/blog_functions.R create mode 100644 scripts/blog_proposal.R diff --git a/scripts/blog-events.R b/scripts/blog-events.R deleted file mode 100644 index cdb1d67263..0000000000 --- a/scripts/blog-events.R +++ /dev/null @@ -1,94 +0,0 @@ -library(airtabler) -library(tidyverse) -library(glue) -library(readr) - -# Setup ---- -api_key <- Sys.setenv("AIRTABLE_API_KEY") -base_id <- Sys.getenv("AIRTABLE_BASE_ID") -table_name <- Sys.getenv("AIRTABLE_TABLE_ID") - - -# Connect to base ---- -base <- airtable(base = base_id, - tables = c("Proposals", "Event Reports")) - -# Get data ---- -events_data <- base$`Event Reports`$select() -str(events_data) - -events_to_draft <- events_data %>% - filter(`Status` == "In progress", Decision == "Accept") - -# @Mo, I tried and failed all morning to make a Status column in the event report like the one in proposals. -# I guess it could also have a manual toggle -# From here it assumes there is a "Status" column) - -if (nrow(events_to_draft) == 0) { - print("No new event reports to draft") - quit() -} - -print(paste("Found", nrow(events_to_draft), "new event report(s) to draft.")) - -for (i in 1:nrow(events_to_draft)) { - - report <- events_to_draft[i, ] - - slug <- report$title %>% - str_to_lower() %>% - str_replace_all("[^a-z0-9\\s-]", "") %>% - str_replace_all("\\s+", "-") - - # I use the *submission date* (`createdTime`) for the post date, - # as the event `date` will always be in the past. - post_date <- ymd_hms(report$createdTime) - year <- year(post_date) - month_day_slug <- paste0(format(post_date, "%m-%d"), "-", slug) - - file_path <- file.path("content", "blog", year, month_day_slug) - - dir.create(file_path, recursive = TRUE, showWarnings = FALSE) - - # Markdown front-matter ---- - front_matter <- glue( - '--- - title: "Event Report: {report$title}" - author: "{report$author}" - date: "{format(post_date, "%Y-%m-%d")}" - tags: [{report$keywords}] - --- - - ' - ) - - # standardised blogpost - post_body <- glue( - 'On {report$date}, {report$chapter} hosted the event "{report$title}". - The session was led by {report$speakers} and was attended by {report$participants} participants. - - ## Event Summary - - Here are the key topics that were covered: - - {report$summary} - - ## Attendee Feedback - - > {report$`Quotes or Reactions (optional)`} - - ## Resources - - A big thank you to the speakers and everyone who attended!' - ) - - final_content <- paste0(front_matter, post_body) - final_file_name <- file.path(file_path, "index.en.md") - - writeLines(final_content, final_file_name) - - print(paste("Successfully created event report file:", final_file_name)) - - # next: logic to update Airtable -} - diff --git a/scripts/blog-proposal.R b/scripts/blog-proposal.R deleted file mode 100644 index b7142d9bda..0000000000 --- a/scripts/blog-proposal.R +++ /dev/null @@ -1,70 +0,0 @@ -library(airtabler) -library(tidyverse) -library(glue) -library(readr) - -# Setup ---- -api_key <- Sys.setenv("AIRTABLE_API_KEY") -base_id <- Sys.getenv("AIRTABLE_BASE_ID") -table_name <- Sys.getenv("AIRTABLE_TABLE_ID") - -# Connect to base ---- -base <- airtable(base = base_id, - tables = c("Proposals", "Event Reports")) - -# Get data ---- -proposals_data <- base$Proposals$select() -events_data <- base$`Event Reports`$select() - -str(proposals_data) -str(events_data) - -proposals_to_draft <- proposals_data %>% - filter(`Status` == "In progress", Decision == "Accept") - -if (nrow(proposals_to_draft) == 0) { - print("No new proposals to draft.") - quit() -} - -print(paste("Found", nrow(proposals_to_draft), "new proposal(s) to draft.")) - -# Loop through the proposals and create md file ---- -for (i in 1:nrow(proposals_to_draft)) { - proposal <- proposals_to_draft - proposal <- proposals_to_draft[i, ] - - # "slug" from the title --- - slug <- proposal$Title %>% - str_to_lower() %>% - str_replace_all("[^a-z0-9\\s-]", "") %>% - str_replace_all("\\s+", "-") - - post_date_obj <- ymd(proposal$`Post date`) - month_day <- format(post_date_obj, "%m-%d") - year <- year(post_date_obj) - final_name_part <- paste0(month_day, "_", slug) - - file_path <- file.path("content", "blog", year, final_name_part) - dir.create(file_path, recursive = TRUE, showWarnings = FALSE) # @Mo, recursive=TRUE to keep? - - # metadata for md file - front_matter <- glue::glue( - '--- - title: "{proposal$Title}" - author: "{proposal$Author}" - date: "{proposal$`Post date`}" - --- - - ' - ) - body_content <- proposal$Description - blog_file <- file.path(file_path, "index.en.md") - - writeLines(paste0(front_matter, body_content), blog_file) - - print(paste("Successfully created draft file:", blog_file)) - - # Next, logic to update Airtable? - -} diff --git a/scripts/blog_events.R b/scripts/blog_events.R new file mode 100644 index 0000000000..9d7eed7aaf --- /dev/null +++ b/scripts/blog_events.R @@ -0,0 +1,103 @@ +library(airtabler) +library(tidyverse) +library(glue) +library(readr) + +source("scripts/blog_functions.R") + +# Get data and draft blogposts---- +process_events <- function(base_id) { + + # Connect and get data + base <- connect_to_airtable(base_id, tables = "Event Reports") + events_data <- base$`Event Reports`$select() + + # Filter ready-to-draft events + events_to_draft <- filter_ready_to_draft(events_data, title_col = "title") + + if (nrow(events_to_draft) == 0) { + message("No new event reports to draft.") + return(0) + } + + message("Found ", nrow(events_to_draft), " new event report(s) to draft.") + + # Process each event + for (i in 1:nrow(events_to_draft)) { + report <- events_to_draft[i, ] + + tryCatch({ + # Validate required fields + if (!validate_required_fields(report, c("title", "createdTime"))) { + next + } + + # Create slug and paths + slug <- create_slug(report$title) + post_date <- ymd_hms(report$createdTime) + year <- year(post_date) + month_day_slug <- paste0(format(post_date, "%m-%d"), "-", slug) + + # Create folder + folder_path <- create_post_folder(year, month_day_slug) + + # Download images if they exist + image_markdown <- "" + if (!is.na(report$media) && report$media != "") { + message("Downloading images for event: ", report$title) + downloaded_images <- download_images(report$media, folder_path) + + # Create markdown for images + if (!is.null(downloaded_images) && length(downloaded_images) > 0) { + image_markdown <- create_image_markdown(downloaded_images) + } + } + # Markdown front-matter ---- + + front_matter <- glue( + '--- + title: "Event Report: {report$Title}" + author: "{report$Author}" + date: "{format(post_date, "%Y-%m-%d")}" + tags: [{report$Keywords}] + --- + ' + ) + # standardised blogpost + + post_body <- glue( + 'On {report$Date}, {report$Chapter} hosted the event "{report$Title}". + The session was led by {report$Speakers} and was attended by {report$Participants} participants. + + ## Event Summary + + Here are the key topics that were covered: + + {report$Summary} + + ## Attendee Feedback + + > {report$`Quotes or Reactions (optional)`} + + ## Resources + + A big thank you to the speakers and everyone who attended!' + ) + + # Write file + content <- paste0(front_matter, post_body) + filepath <- write_markdown_file(content, folder_path) + message("✓ Successfully created: ", filepath) + + }, error = function(e) { + warning("Error processing event ", i, ": ", e$message) + }) + } + + return(nrow(events_to_draft)) +} + +# After successfully creating the markdown file: +# Update the Airtable record to: +# - Status = "Post drafted" +# - Add PR URL \ No newline at end of file diff --git a/scripts/blog_functions.R b/scripts/blog_functions.R new file mode 100644 index 0000000000..7668204c3f --- /dev/null +++ b/scripts/blog_functions.R @@ -0,0 +1,58 @@ +library(tidyverse) +library(glue) +library(httr) +library(airtabler) + +# Connect to Airtable base +connect_to_airtable <- function(base_id, tables) { + message("Connecting to Airtable...") + base <- airtable(base = base_id, tables = tables) + return(base) +} + +# Filter data to get records ready for drafting +# Removes the dont-delete row and filters for In progress + Accept status +filter_ready_to_draft <- function(data, title_col = "Title") { + data %>% + filter( + .data[[title_col]] != "dont-delete", + Status == "In progress", + Decision == "Accept" + ) +} + +# Create a URL-friendly slug from text +create_slug <- function(text) { + text %>% + str_to_lower() %>% + str_replace_all("[^a-z0-9\\s-]", "") %>% + str_replace_all("\\s+", "-") +} + +# Create blog post folder structure (content/blog/YYYY/mm-dd-slug/) +create_post_folder <- function(year, month_day_slug) { + folder_path <- file.path("content", "blog", as.character(year), month_day_slug) + dir.create(folder_path, recursive = TRUE, showWarnings = FALSE) + return(folder_path) +} + +# Escape special characters for YAML frontmatter +escape_yaml <- function(text) { + if (is.null(text) || is.na(text) || text == "") return('""') + # Escape quotes and wrap in quotes if text contains special YAML characters + if (str_detect(text, '[":\\n]')) { + text <- str_replace_all(text, '"', '\\\\"') + text <- paste0('"', text, '"') + } + return(text) +} + +# Download images from URLs and save to folder +# Returns vector of local filenames +download_images <- function(image_urls, folder_path) { + + # Handle NULL or empty input + if (is.null(image_urls) || is.na(image_urls) || length(image_urls) == 0 || image_urls == "") { + return(NULL) + } + \ No newline at end of file diff --git a/scripts/blog_proposal.R b/scripts/blog_proposal.R new file mode 100644 index 0000000000..860557c6ef --- /dev/null +++ b/scripts/blog_proposal.R @@ -0,0 +1,74 @@ +library(airtabler) +library(tidyverse) +library(glue) +library(lubridate) + +source("scripts/blog_functions.R") + +# Get data and create draft blogpost ---- +process_proposals <- function(base_id) { + + # Connect and get data + base <- connect_to_airtable(base_id, tables = "Proposals") + proposals_data <- base$Proposals$select() + + # Filter ready-to-draft proposals + proposals_to_draft <- filter_ready_to_draft(proposals_data, title_col = "Title") + + if (nrow(proposals_to_draft) == 0) { + message("No new proposals to draft.") + return(0) + } + + message("Found ", nrow(proposals_to_draft), " new proposal(s) to draft.") + + # Process each proposal + for (i in 1:nrow(proposals_to_draft)) { + proposal <- proposals_to_draft[i, ] + + tryCatch({ + # Validate required fields + if (!validate_required_fields(proposal, c("Title", "Post date"))) { + next + } + + # Create slug and paths + slug <- create_slug(proposal$Title) + post_date_obj <- ymd(proposal$`Post date`) + year <- year(post_date_obj) + month_day_slug <- paste0(format(post_date_obj, "%m-%d"), "-", slug) + + # Create folder + folder_path <- create_post_folder(year, month_day_slug) + + # Build content + front_matter <- glue( + '--- +title: {escape_yaml(proposal$Title)} +author: {escape_yaml(proposal$Author)} +date: "{proposal$`Post date`}" +slug: {slug} +--- + +' + ) + + content <- paste0(front_matter, proposal$Description) + + # Write file + filepath <- write_markdown_file(content, folder_path) + message("✓ Successfully created: ", filepath) + + }, error = function(e) { + warning("Error processing proposal ", i, ": ", e$message) + }) + } + + return(nrow(proposals_to_draft)) +} + + +# After successfully creating the markdown file: +# Update the Airtable record to: +# - Status = "Post drafted" +# - Add PR URL (once you know it) \ No newline at end of file From 143b9a395afae110f886e0d1c4fd0904d684f7b6 Mon Sep 17 00:00:00 2001 From: cecibaldoni Date: Sat, 4 Oct 2025 12:46:58 +0200 Subject: [PATCH 3/4] fixed blog_function missing parenthesis --- scripts/blog_functions.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/blog_functions.R b/scripts/blog_functions.R index 7668204c3f..1490aec0fe 100644 --- a/scripts/blog_functions.R +++ b/scripts/blog_functions.R @@ -55,4 +55,4 @@ download_images <- function(image_urls, folder_path) { if (is.null(image_urls) || is.na(image_urls) || length(image_urls) == 0 || image_urls == "") { return(NULL) } - \ No newline at end of file +} \ No newline at end of file From eaf5a0c269aa4a29f65ec6ce0bde3506afcedb04 Mon Sep 17 00:00:00 2001 From: cecibaldoni Date: Sat, 7 Mar 2026 16:58:11 +0100 Subject: [PATCH 4/4] fixed images handling and added github workflow --- .github/workflows/generate-blog-drafts.yml | 68 ++++++++++++++++++++++ scripts/blog_events.R | 42 +++++++------ scripts/blog_functions.R | 37 ++++++++++-- scripts/blog_proposal.R | 2 + 4 files changed, 125 insertions(+), 24 deletions(-) create mode 100644 .github/workflows/generate-blog-drafts.yml diff --git a/.github/workflows/generate-blog-drafts.yml b/.github/workflows/generate-blog-drafts.yml new file mode 100644 index 0000000000..b5777193c5 --- /dev/null +++ b/.github/workflows/generate-blog-drafts.yml @@ -0,0 +1,68 @@ +jobs: + generate-drafts: + name: Generate blog drafts + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ssh-key: ${{ secrets.push-to-protected }} + + - name: Install cURL headers + run: | + sudo apt-get update + sudo apt-get install libcurl4-openssl-dev + + - name: Setup R + uses: r-lib/actions/setup-r@v2 + with: + r-version: 'release' + + - name: Install R packages + run: | + install.packages(c("airtabler", "tidyverse", "glue", "lubridate", "httr", "readr"), + repos = "https://cloud.r-project.org") + shell: Rscript {0} + + - name: Run proposal script + env: + AIRTABLE_API_KEY: ${{ secrets.AIRTABLE_API_KEY }} + AIRTABLE_BASE_ID: ${{ secrets.AIRTABLE_BASE_ID }} + run: Rscript scripts/blog_proposal.R + + - name: Run events script + env: + AIRTABLE_API_KEY: ${{ secrets.AIRTABLE_API_KEY }} + AIRTABLE_BASE_ID: ${{ secrets.AIRTABLE_BASE_ID }} + run: Rscript scripts/blog_events.R + + - name: Check for new files + id: check_files + run: | + if [ -n "$(git status --porcelain content/blog)" ]; then + echo "new_files=true" >> $GITHUB_OUTPUT + else + echo "new_files=false" >> $GITHUB_OUTPUT + fi + + - name: Configure Git + if: steps.check_files.outputs.new_files == 'true' + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + - name: Open PR with new drafts + if: steps.check_files.outputs.new_files == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + BRANCH="blog-drafts-$(date +%Y-%m-%d-%H%M)" + git checkout -b $BRANCH + git add content/blog + git commit -m "chore: add blog drafts from Airtable $(date +%Y-%m-%d)" + git push origin $BRANCH + gh pr create \ + --title "Blog drafts $(date +%Y-%m-%d)" \ + --body "Auto-generated blog post drafts from Airtable. Please review before merging." \ + --base main \ No newline at end of file diff --git a/scripts/blog_events.R b/scripts/blog_events.R index 9d7eed7aaf..9d53e3de61 100644 --- a/scripts/blog_events.R +++ b/scripts/blog_events.R @@ -13,7 +13,7 @@ process_events <- function(base_id) { events_data <- base$`Event Reports`$select() # Filter ready-to-draft events - events_to_draft <- filter_ready_to_draft(events_data, title_col = "title") + events_to_draft <- filter_ready_to_draft(events_data, title_col = "Title") if (nrow(events_to_draft) == 0) { message("No new event reports to draft.") @@ -63,26 +63,27 @@ process_events <- function(base_id) { --- ' ) - # standardised blogpost post_body <- glue( - 'On {report$Date}, {report$Chapter} hosted the event "{report$Title}". - The session was led by {report$Speakers} and was attended by {report$Participants} participants. - - ## Event Summary - - Here are the key topics that were covered: - - {report$Summary} - - ## Attendee Feedback - - > {report$`Quotes or Reactions (optional)`} - - ## Resources - - A big thank you to the speakers and everyone who attended!' - ) + 'On {report$Date}, {report$Chapter} hosted the event "{report$Title}". + The session was led by {report$Speakers} and was attended by {report$Participants} participants. + + ## Event Summary + + {report$Summary} + + ## Attendee Feedback + + > {report$`Quotes or Reactions (optional)`} + + ## Resources + + {report$Resources} + + A big thank you to the speakers and everyone who attended! + + {image_markdown}' + ) # Write file content <- paste0(front_matter, post_body) @@ -97,6 +98,9 @@ process_events <- function(base_id) { return(nrow(events_to_draft)) } +base_id <- Sys.getenv("AIRTABLE_BASE_ID") +process_events(base_id) + # After successfully creating the markdown file: # Update the Airtable record to: # - Status = "Post drafted" diff --git a/scripts/blog_functions.R b/scripts/blog_functions.R index 1490aec0fe..547e8d86d3 100644 --- a/scripts/blog_functions.R +++ b/scripts/blog_functions.R @@ -47,12 +47,39 @@ escape_yaml <- function(text) { return(text) } -# Download images from URLs and save to folder -# Returns vector of local filenames +# Download images from Airtable attachment field and save to folder +# Returns vector of local filenames (or empty vector if none) download_images <- function(image_urls, folder_path) { - # Handle NULL or empty input - if (is.null(image_urls) || is.na(image_urls) || length(image_urls) == 0 || image_urls == "") { - return(NULL) + # Handle NULL, NA, empty, or non-list input + if (is.null(image_urls) || length(image_urls) == 0) return(character(0)) + + # Airtable returns attachments as a list of lists, each with $url and $filename + # Coerce to list if it somehow comes in as a single item + if (!is.list(image_urls)) return(character(0)) + + downloaded <- c() + + for (attachment in image_urls) { + tryCatch({ + url <- attachment$url + filename <- attachment$filename + + if (is.null(url) || is.null(filename)) next + + local_path <- file.path(folder_path, filename) + response <- httr::GET(url, httr::write_disk(local_path, overwrite = TRUE)) + + if (httr::status_code(response) == 200) { + downloaded <- c(downloaded, filename) + message(" ↓ Downloaded: ", filename) + } else { + warning(" ✗ Failed to download: ", filename, " (HTTP ", httr::status_code(response), ")") + } + }, error = function(e) { + warning(" ✗ Error downloading image: ", e$message) + }) } + + return(downloaded) } \ No newline at end of file diff --git a/scripts/blog_proposal.R b/scripts/blog_proposal.R index 860557c6ef..ee9b102d0a 100644 --- a/scripts/blog_proposal.R +++ b/scripts/blog_proposal.R @@ -67,6 +67,8 @@ slug: {slug} return(nrow(proposals_to_draft)) } +base_id <- Sys.getenv("AIRTABLE_BASE_ID") +process_proposals(base_id) # After successfully creating the markdown file: # Update the Airtable record to: