228 lines
7.5 KiB
Text
228 lines
7.5 KiB
Text
|
#!/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 "$@"
|