blog/scripts/social-cards-zola

228 lines
7.5 KiB
Text
Raw Normal View History

2024-02-17 20:58:54 +01:00
#!/usr/bin/env bash
set -eo pipefail
# This script takes a markdown post, crafts the corresponding URL, checks if it's accessible,
# takes a screenshot, and saves it to a specified location.
# It can update the front matter of the post with the path to the generated image (-u | --update-front-matter option).
# It's meant to be used as a pre-commit hook to generate social media cards for Zola sites using the tabi theme.
# More details: https://osc.garden/blog/automating-social-media-cards-zola/
function help_function(){
echo "This script automates the creation of social media cards for Zola websites."
echo "It takes a Markdown post and saves its live screenshot to a specified location."
echo ""
echo "IMPORTANT! It needs to be run from the root of the Zola site."
echo ""
echo "Usage: social-cards-zola [OPTIONS]"
echo ""
echo "Options:"
echo " -h, --help Show this help message and exit."
echo " -b, --base_url URL The base URL where the Zola site is hosted. Default is http://127.0.0.1:1111."
echo " -i, --input INPUT_PATH The relative path to the markdown file of the post/section you want to capture. Should be in the format 'content/blog/post_name.language.md'."
echo " -k, --key KEY The front matter key to update. Default is 'social_media_card'."
echo " -o, --output_path PATH The directory where the generated image will be saved."
echo " -p, --print_output Print the path to the resulting screenshot at the end."
echo " -u, --update-front-matter Update or add the 'social_media_card' key in the front matter of the Markdown file."
echo
echo "Examples:"
echo " social-cards-zola --base_url https://example.com --input content/blog/my_post.md --output_path static/img/social_cards"
echo " social-cards-zola -u -b http://127.0.0.1:1025 -i content/archive/_index.es.md -o static/img"
exit 0
}
function convert_filename_to_url() {
# Remove .md extension.
local post_name="${1%.md}"
# Remove "content/" prefix.
local url="${post_name#content/}"
# Extract language code.
local lang_code="${url##*.}"
if [[ "$lang_code" == "$url" ]]; then
lang_code="" # No language code.
else
lang_code="${lang_code}/" # Add trailing slash.
url="${url%.*}" # Remove the language code from the URL.
fi
# Handle co-located index.md by stripping it and using the directory as the URL.
if [[ "$url" == */index ]]; then
url="${url%/*}" # Remove the /index suffix.
fi
# Remove "_index" suffix.
if [[ "$url" == *"_index"* ]]; then
url="${url%%_index*}"
fi
# Return the final URL with a single trailing slash.
full_url="${lang_code}${url}"
echo "${full_url%/}/"
}
function error_exit() {
echo "ERROR: $1" >&2
exit "${2:-1}"
}
function validate_input_params() {
missing_params=()
if [[ -z "$base_url" ]]; then
missing_params+=("base_url")
fi
if [[ -z "$input" ]]; then
missing_params+=("input")
fi
if [[ -z "$output_path" ]]; then
missing_params+=("output_path")
fi
if [ ${#missing_params[@]} -ne 0 ]; then
error_exit "The following required settings are missing: ${missing_params[*]}. Use -h or --help for usage."
fi
}
function check_dependencies() {
for cmd in "curl" "shot-scraper"; do
if ! command -v $cmd &> /dev/null; then
error_exit "$cmd could not be found. Please install it."
fi
done
}
function fetch_status() {
local retry_count=0
local max_retries=5
local status
while [[ $retry_count -lt $max_retries ]]; do
status=$(curl -s -o /dev/null -I -w "%{http_code}" "${base_url}${post_url}")
if [[ "$status" -eq "200" ]]; then
return
fi
retry_count=$((retry_count + 1))
sleep 2
done
error_exit "Post $input is not accessible. Max retries ($max_retries) reached."
}
function capture_screenshot() {
temp_file=$(mktemp /tmp/social-zola.XXXXXX)
trap 'rm -f "$temp_file"' EXIT
shot-scraper --silent "${base_url}/${post_url}" -w 700 -h 400 --retina --quality 60 -o "$temp_file"
}
function move_file() {
local safe_filename=$(echo "${post_url%/}" | sed 's/[^a-zA-Z0-9]/_/g')
# Create the output directory if it doesn't exist.
mkdir -p "$output_path"
image_filename="${output_path}/${safe_filename:-index}.jpg" # If the filename is empty, use "index".
mv "$temp_file" "$image_filename" || error_exit "Failed to move the file to $image_filename"
}
function update_front_matter {
local md_file_path="$1"
local image_output="${2#static/}"
# Temporary file for awk processing
temp_awk=$(mktemp /tmp/frontmatter.XXXXXX)
awk -v card_path="$image_output" '
# Initialize flags for tracking state.
BEGIN { in_extra=done=front_matter=extra_exists=0; }
# Function to insert the social_media_card path.
function insert_card() { print "social_media_card = \"" card_path "\""; done=1; }
{
# If card has been inserted, simply output remaining lines.
if (done) { print; next; }
# Toggle front_matter flag at its start, denoted by +++
if (/^\+\+\+/ && front_matter == 0) {
front_matter = 1;
print "+++";
next;
}
# Detect [extra] section and set extra_exists flag.
if (/^\[extra\]/) { in_extra=1; extra_exists=1; print; next; }
# Update existing social_media_card.
if (in_extra && /^social_media_card =/) { insert_card(); in_extra=0; next; }
# End of front matter or start of new section.
if (in_extra && (/^\[[a-zA-Z_-]+\]/ || (/^\+\+\+/ && front_matter == 1))) {
insert_card(); # Add the missing social_media_card.
in_extra=0;
}
# Insert missing [extra] section.
if (/^\+\+\+/ && front_matter == 1 && in_extra == 0 && extra_exists == 0) {
print "\n[extra]";
insert_card();
in_extra=0;
front_matter = 0;
print "+++";
next;
}
# Print all other lines as-is.
print;
}' "$md_file_path" > "$temp_awk"
# Move the temporary file back to the original markdown file.
mv "$temp_awk" "$md_file_path"
}
function main() {
while [[ "$#" -gt 0 ]]; do
case "$1" in
-h|--help)
help_function;;
-b|--base_url)
base_url="$2"
shift 2;;
-i|--input)
input="$2"
shift 2;;
-o|--output_path)
output_path="$2"
shift 2;;
-k|--key)
front_matter_key="$2"
shift 2;;
-u|--update-front-matter)
update="true"
shift 1;;
-p|--print_output)
print_output="true"
shift 1;;
*)
error_exit "Unknown option: $1";;
esac
done
validate_input_params
check_dependencies
: "${base_url:="http://127.0.0.1:1111"}"
: "${front_matter_key:="social_media_card"}"
base_url="${base_url%/}/" # Ensure one trailing slash.
post_url="$(convert_filename_to_url "$input")"
fetch_status
capture_screenshot
move_file
if [[ "$update" == "true" ]]; then
update_front_matter "$input" "$image_filename"
fi
if [[ "$print_output" == "true" ]]; then
echo "$image_filename"
fi
}
main "$@"