|
| 1 | +#!/usr/bin/env bash |
| 2 | +set -euo pipefail |
| 3 | + |
| 4 | +SITE_URL="https://hypemd.dev" |
| 5 | +HANDLE="@hype_markdown" |
| 6 | +MAX_CHARS=280 |
| 7 | + |
| 8 | +usage() { |
| 9 | + echo "Usage: $0 <content-slug>" |
| 10 | + echo "" |
| 11 | + echo "Generate X/Twitter post variants for a blog post." |
| 12 | + echo "" |
| 13 | + echo "Examples:" |
| 14 | + echo " $0 getting-started" |
| 15 | + echo " $0 ai-authoring-workflow" |
| 16 | + exit 1 |
| 17 | +} |
| 18 | + |
| 19 | +if [[ $# -lt 1 ]]; then |
| 20 | + usage |
| 21 | +fi |
| 22 | + |
| 23 | +SLUG="$1" |
| 24 | +FILE="content/${SLUG}/module.md" |
| 25 | + |
| 26 | +if [[ ! -f "$FILE" ]]; then |
| 27 | + echo "Error: $FILE not found" >&2 |
| 28 | + exit 1 |
| 29 | +fi |
| 30 | + |
| 31 | +title=$(grep -m1 '^# ' "$FILE" | sed 's/^# //') |
| 32 | +details=$(awk '/<details>/{found=1; next} /<\/details>/{if(found) exit} found{print}' "$FILE") |
| 33 | +slug=$(echo "$details" | grep '^slug:' | head -1 | sed 's/^slug: *//') |
| 34 | +seo_desc=$(echo "$details" | grep '^seo_description:' | head -1 | sed 's/^seo_description: *//') |
| 35 | +tags=$(echo "$details" | grep '^tags:' | head -1 | sed 's/^tags: *//') |
| 36 | +author=$(echo "$details" | grep '^author:' | head -1 | sed 's/^author: *//') |
| 37 | + |
| 38 | +if [[ -z "$title" ]]; then |
| 39 | + echo "Error: could not parse title from $FILE" >&2 |
| 40 | + exit 1 |
| 41 | +fi |
| 42 | + |
| 43 | +if [[ "$slug" == docs/* || "$slug" == "docs" ]]; then |
| 44 | + echo "WARNING: This appears to be a documentation page (slug: ${slug}). Docs pages are typically not promoted on social media." >&2 |
| 45 | + echo "" >&2 |
| 46 | +fi |
| 47 | + |
| 48 | +is_tutorial=false |
| 49 | +if echo "$tags" | grep -qi 'tutorial'; then |
| 50 | + is_tutorial=true |
| 51 | +fi |
| 52 | + |
| 53 | +build_url() { |
| 54 | + local variant="$1" |
| 55 | + echo "${SITE_URL}/${slug}/?utm_source=twitter&utm_medium=social&utm_content=${variant}" |
| 56 | +} |
| 57 | + |
| 58 | +print_variant() { |
| 59 | + local label="$1" |
| 60 | + local text="$2" |
| 61 | + local chars=${#text} |
| 62 | + echo "=== ${label} ===" |
| 63 | + echo "$text" |
| 64 | + echo "" |
| 65 | + echo "Characters: ${chars}" |
| 66 | + if [[ $chars -gt $MAX_CHARS ]]; then |
| 67 | + echo "WARNING: Exceeds ${MAX_CHARS} character limit by $((chars - MAX_CHARS)) characters" |
| 68 | + fi |
| 69 | + echo "" |
| 70 | +} |
| 71 | + |
| 72 | +build_hashtags() { |
| 73 | + local -a all=("#HypeMarkdown" "#Golang" "#OpenSource") |
| 74 | + |
| 75 | + IFS=',' read -ra tag_arr <<< "$tags" |
| 76 | + for tag in "${tag_arr[@]}"; do |
| 77 | + tag=$(echo "$tag" | sed 's/^ *//;s/ *$//') |
| 78 | + case "$tag" in |
| 79 | + tutorial|getting-started|hype) ;; |
| 80 | + docker) all+=("#Docker") ;; |
| 81 | + ai|claude) all+=("#AI") ;; |
| 82 | + workflow) all+=("#DevWorkflow") ;; |
| 83 | + authoring) all+=("#TechWriting") ;; |
| 84 | + training) all+=("#Training") ;; |
| 85 | + documentation|docs) all+=("#Documentation") ;; |
| 86 | + release*) all+=("#ReleaseNotes") ;; |
| 87 | + handbook) all+=("#EngineeringHandbook") ;; |
| 88 | + *) all+=("#${tag^}") ;; |
| 89 | + esac |
| 90 | + done |
| 91 | + |
| 92 | + local seen="" |
| 93 | + local result="" |
| 94 | + for h in "${all[@]}"; do |
| 95 | + if [[ "$seen" != *"$h"* ]]; then |
| 96 | + result="$result $h" |
| 97 | + seen="$seen $h" |
| 98 | + fi |
| 99 | + done |
| 100 | + echo "${result# }" |
| 101 | +} |
| 102 | + |
| 103 | +url_technical=$(build_url "technical") |
| 104 | +url_founder=$(build_url "founder") |
| 105 | +url_hook=$(build_url "hook") |
| 106 | + |
| 107 | +if [[ "$is_tutorial" == true ]]; then |
| 108 | + technical="${seo_desc} ${url_technical}" |
| 109 | + founder="We built Hype because documentation shouldn't lie. Here's how to get started with dynamic Markdown that validates everything at build time: ${url_founder}" |
| 110 | + hook="Your Markdown can run code now. ${url_hook}" |
| 111 | + |
| 112 | + case "$slug" in |
| 113 | + getting-started) |
| 114 | + technical="Learn how to install Hype and create your first dynamic Markdown document with build-time code execution. ${url_technical}" |
| 115 | + founder="We built Hype because documentation shouldn't lie. Here's a quick guide to get started: ${url_founder}" |
| 116 | + hook="What if your Markdown could execute code and catch errors before publish? ${url_hook}" |
| 117 | + ;; |
| 118 | + deploying-with-docker) |
| 119 | + technical="Deploy a Hype-powered blog with Docker — from Dockerfile to production with auto-rebuilds. ${url_technical}" |
| 120 | + founder="We wanted deploying a Hype blog to be as simple as 'docker build && docker run'. Here's how: ${url_founder}" |
| 121 | + hook="Ship your Hype blog in a container. ${url_hook}" |
| 122 | + ;; |
| 123 | + *) |
| 124 | + technical="${seo_desc} ${url_technical}" |
| 125 | + founder="We built this with Hype because ${title,,} shouldn't be harder than it needs to be: ${url_founder}" |
| 126 | + hook="${title} — powered by dynamic Markdown. ${url_hook}" |
| 127 | + ;; |
| 128 | + esac |
| 129 | +else |
| 130 | + technical="${seo_desc} ${url_technical}" |
| 131 | + founder="We've been using Hype for ${title,,} and it's been a game changer. Here's how: ${url_founder}" |
| 132 | + hook="${title} — see how teams are using Hype. ${url_hook}" |
| 133 | +fi |
| 134 | + |
| 135 | +echo "========================================" |
| 136 | +echo "X/Twitter Posts for: ${title}" |
| 137 | +echo "Post type: $(if $is_tutorial; then echo 'Tutorial'; else echo 'Usage Scenario'; fi)" |
| 138 | +echo "========================================" |
| 139 | +echo "" |
| 140 | + |
| 141 | +print_variant "TECHNICAL VARIANT" "$technical" |
| 142 | +print_variant "FOUNDER VOICE VARIANT" "$founder" |
| 143 | +print_variant "SHORT HOOK VARIANT" "$hook" |
| 144 | + |
| 145 | +hashtags=$(build_hashtags) |
| 146 | +echo "=== HASHTAGS ===" |
| 147 | +echo "$hashtags" |
| 148 | +echo "" |
| 149 | + |
| 150 | +if [[ "$is_tutorial" == true ]]; then |
| 151 | + url_thread=$(build_url "thread") |
| 152 | + echo "=== THREAD VARIANT (3 posts) ===" |
| 153 | + echo "" |
| 154 | + echo "1/3:" |
| 155 | + echo "${seo_desc}" |
| 156 | + echo "" |
| 157 | + echo "A thread on ${title,,} with @hype_markdown 🧵" |
| 158 | + echo "" |
| 159 | + echo "2/3:" |
| 160 | + first_section=$(sed -n '/^## /{s/^## //;p;q;}' "$FILE") |
| 161 | + if [[ -n "$first_section" ]]; then |
| 162 | + echo "It starts with ${first_section,,} — Hype makes this straightforward because your Markdown is dynamic. Code blocks execute, files get included, and everything is validated at build time." |
| 163 | + else |
| 164 | + echo "Hype makes this straightforward because your Markdown is dynamic. Code blocks execute, files get included, and everything is validated at build time." |
| 165 | + fi |
| 166 | + echo "" |
| 167 | + echo "3/3:" |
| 168 | + echo "Full walkthrough here: ${url_thread}" |
| 169 | + echo "" |
| 170 | + echo "${hashtags}" |
| 171 | + echo "" |
| 172 | +fi |
| 173 | + |
| 174 | +echo "=== UTM URLS ===" |
| 175 | +echo "Technical: ${url_technical}" |
| 176 | +echo "Founder: ${url_founder}" |
| 177 | +echo "Hook: ${url_hook}" |
| 178 | +if [[ "$is_tutorial" == true ]]; then |
| 179 | + echo "Thread: $(build_url "thread")" |
| 180 | +fi |
0 commit comments