GM
This commit is contained in:
commit
47eea16caf
49
1/README.md
Normal file
49
1/README.md
Normal file
@ -0,0 +1,49 @@
|
||||
# Publish Markdown articles to Nostr
|
||||
|
||||
I'm currently using [this bash script][1] to publish [long-form content][2]
|
||||
from local Markdown files to Nostr relays.
|
||||
|
||||
It requires all of `yq`, `jq`, and `nak` to be installed.
|
||||
|
||||
## Usage
|
||||
|
||||
Create a signed Nostr event and print it to the console:
|
||||
|
||||
markdown_to_nostr.sh article-filename.md
|
||||
|
||||
Create a Nostr event and publish it to one or more relays:
|
||||
|
||||
markdown_to_nostr.sh article-filename.md ws://localhost:7777 wss://nostr.kosmos.org
|
||||
|
||||
## Markdown format
|
||||
|
||||
You can specify your metadata as YAML in a Front Matter header. Here's an
|
||||
example file:
|
||||
|
||||
```md
|
||||
---
|
||||
title: "Good Morning"
|
||||
summary: "It's a beautiful day"
|
||||
image: https://example.com/i/beautiful-day.jpg
|
||||
date: 2025-04-24T15:00:00Z
|
||||
tags: gm, poetry
|
||||
published: false
|
||||
---
|
||||
|
||||
In the blue sky just a few specks of gray
|
||||
In the evening of a beautiful day
|
||||
Though last night it rained and more rain on the way
|
||||
And that more rain is needed 'twould be fair to say.
|
||||
|
||||
— Francis Duggan
|
||||
```
|
||||
|
||||
The metadata keys are mostly self-explanatory. Note:
|
||||
|
||||
* All keys except for `title` are optional
|
||||
* `date`, if present, will be set as the `published_at` date.
|
||||
* If `published` is set to `true`, it will publish a kind 30023 event,
|
||||
otherwise a kind 30024 (draft)
|
||||
|
||||
[1]: https://gitea.kosmos.org/raucao/gists/src/branch/master/1/markdown_to_nostr.sh
|
||||
[2]: https://github.com/nostr-protocol/nips/blob/master/23.md
|
176
1/markdown_to_nostr.sh
Executable file
176
1/markdown_to_nostr.sh
Executable file
@ -0,0 +1,176 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Check if yq, jq, and nak are installed
|
||||
if ! command -v yq &> /dev/null; then
|
||||
echo "Error: yq is not installed. Please install yq to proceed."
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v jq &> /dev/null; then
|
||||
echo "Error: jq is not installed. Please install jq to proceed."
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v nak &> /dev/null; then
|
||||
echo "Error: nak is not installed. Please install nak to proceed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if at least one input file is provided
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "Usage: $0 <markdown_file.md> [relay...]"
|
||||
echo "Converts a Markdown file with front matter to a Nostr event (kind 30023 if published: true, else kind 30024), prompts for secret key, and optionally publishes to relays."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MD_FILE="$1"
|
||||
shift # Remove MD_FILE from arguments, leaving relays (if any)
|
||||
|
||||
# Check if file exists and is a Markdown file
|
||||
if [ ! -f "$MD_FILE" ] || [[ ! "$MD_FILE" =~ \.md$ ]]; then
|
||||
echo "Error: '$MD_FILE' is not a valid .md file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract filename without .md extension for d tag
|
||||
D_TAG=$(basename "$MD_FILE" .md)
|
||||
|
||||
# Extract front matter (between --- delimiters)
|
||||
FRONT_MATTER=$(awk '
|
||||
BEGIN { in_front_matter=0; front_matter_count=0 }
|
||||
/^---$/ {
|
||||
front_matter_count++
|
||||
if (front_matter_count == 1) { in_front_matter=1; next }
|
||||
else if (front_matter_count == 2) { in_front_matter=0; exit }
|
||||
}
|
||||
in_front_matter { print }
|
||||
' "$MD_FILE")
|
||||
|
||||
# Check if front matter is empty or invalid
|
||||
if [ -z "$FRONT_MATTER" ]; then
|
||||
echo "Error: '$MD_FILE' has no valid front matter"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Parse front matter with yq using a temporary file
|
||||
echo "$FRONT_MATTER" > .temp_front_matter.yaml
|
||||
FRONT_MATTER_JSON=$(yq -r '.' .temp_front_matter.yaml 2>/dev/null)
|
||||
rm .temp_front_matter.yaml
|
||||
|
||||
if [ $? -ne 0 ] || [ -z "$FRONT_MATTER_JSON" ]; then
|
||||
echo "Error: '$MD_FILE' has invalid YAML front matter"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract title, summary, date, tags, image, and published from front matter
|
||||
TITLE=$(echo "$FRONT_MATTER_JSON" | jq -r '.title // ""')
|
||||
SUMMARY=$(echo "$FRONT_MATTER_JSON" | jq -r '.summary // ""')
|
||||
DATE=$(echo "$FRONT_MATTER_JSON" | jq -r '.date // ""')
|
||||
TAGS=$(echo "$FRONT_MATTER_JSON" | jq -r '.tags // ""')
|
||||
IMAGE=$(echo "$FRONT_MATTER_JSON" | jq -r '.image // ""')
|
||||
PUBLISHED=$(echo "$FRONT_MATTER_JSON" | jq -r '.published // "false"')
|
||||
|
||||
# Convert ISO 8601 date to Unix timestamp for published_at (if valid)
|
||||
if [ -n "$DATE" ]; then
|
||||
PUBLISHED_AT=$(date -d "$DATE" +%s 2>/dev/null || echo "$DATE")
|
||||
else
|
||||
PUBLISHED_AT=""
|
||||
fi
|
||||
|
||||
# Extract content (everything after front matter) and trim only leading/trailing blank lines
|
||||
CONTENT=$(awk '
|
||||
BEGIN { in_front_matter=0; front_matter_count=0; content_started=0 }
|
||||
/^---$/ {
|
||||
front_matter_count++
|
||||
if (front_matter_count == 1) { in_front_matter=1 }
|
||||
else if (front_matter_count == 2) { in_front_matter=0; next }
|
||||
}
|
||||
!in_front_matter && front_matter_count >= 2 {
|
||||
if (!content_started && $0 ~ /^[[:space:]]*$/) {
|
||||
next # Skip leading blank lines
|
||||
}
|
||||
content_started=1
|
||||
content = content $0 "\n"
|
||||
}
|
||||
END {
|
||||
# Trim trailing blank lines
|
||||
if (content != "") {
|
||||
sub(/\n*[[:space:]]*\n*$/, "", content)
|
||||
print content
|
||||
}
|
||||
}
|
||||
' "$MD_FILE")
|
||||
|
||||
# Check if content is empty
|
||||
if [ -z "$CONTENT" ]; then
|
||||
echo "Error: No content found after front matter in '$MD_FILE'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Write content to a temporary file to avoid escaping issues
|
||||
TEMP_CONTENT=$(mktemp)
|
||||
echo -n "$CONTENT" > "$TEMP_CONTENT"
|
||||
|
||||
# Determine event kind based on published status
|
||||
if [ "$PUBLISHED" = "true" ]; then
|
||||
EVENT_KIND=30023
|
||||
else
|
||||
EVENT_KIND=30024
|
||||
fi
|
||||
|
||||
# Build nak command with --prompt-sec
|
||||
NAK_CMD="nak event --kind $EVENT_KIND --content \"\$(cat $TEMP_CONTENT)\" --prompt-sec"
|
||||
|
||||
# Add tags
|
||||
NAK_CMD="$NAK_CMD -d \"$D_TAG\""
|
||||
if [ -n "$TITLE" ]; then
|
||||
NAK_CMD="$NAK_CMD --tag title=\"$TITLE\""
|
||||
fi
|
||||
if [ -n "$SUMMARY" ]; then
|
||||
NAK_CMD="$NAK_CMD --tag summary=\"$SUMMARY\""
|
||||
fi
|
||||
if [ -n "$PUBLISHED_AT" ]; then
|
||||
NAK_CMD="$NAK_CMD --tag published_at=\"$PUBLISHED_AT\""
|
||||
fi
|
||||
if [ -n "$IMAGE" ]; then
|
||||
NAK_CMD="$NAK_CMD --tag image=\"$IMAGE\""
|
||||
fi
|
||||
|
||||
# Add t tags from comma-separated front matter tags
|
||||
if [ -n "$TAGS" ]; then
|
||||
# Split comma-separated tags, trim whitespace, and add as t tags
|
||||
IFS=',' read -ra TAG_ARRAY <<< "$TAGS"
|
||||
for TAG in "${TAG_ARRAY[@]}"; do
|
||||
# Trim leading/trailing whitespace from tag
|
||||
TAG=$(echo "$TAG" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
|
||||
if [ -n "$TAG" ]; then
|
||||
NAK_CMD="$NAK_CMD --tag t=\"$TAG\""
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Add relays (if provided)
|
||||
for RELAY in "$@"; do
|
||||
NAK_CMD="$NAK_CMD \"$RELAY\""
|
||||
done
|
||||
|
||||
# Create a temporary file for nak output
|
||||
TEMP_OUTPUT=$(mktemp)
|
||||
|
||||
# Execute nak command, directing prompt to terminal and capturing output
|
||||
# Use /dev/tty for stdin to ensure prompt is interactive
|
||||
eval "$NAK_CMD > $TEMP_OUTPUT" < /dev/tty
|
||||
NAK_EXIT=$?
|
||||
|
||||
# Read the output from the temporary file
|
||||
EVENT_JSON=$(cat "$TEMP_OUTPUT")
|
||||
|
||||
# Clean up temporary files
|
||||
rm "$TEMP_CONTENT" "$TEMP_OUTPUT"
|
||||
|
||||
# Check if nak command succeeded
|
||||
if [ $NAK_EXIT -ne 0 ] || [ -z "$EVENT_JSON" ]; then
|
||||
echo "Error: Failed to create Nostr event with nak (check secret key input)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Output the JSON object on a single line
|
||||
echo "$EVENT_JSON" | jq -c .
|
Loading…
x
Reference in New Issue
Block a user