Last active
February 6, 2026 12:38
-
-
Save huozhi/7285c7f0de4456dd2d47ea5bd0da32a6 to your computer and use it in GitHub Desktop.
copy github pr or issue link to slack
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # gh-slack: Generate Slack-formatted links from GitHub PR/issue URLs | |
| # Usage: gh-slack <github-url> | |
| set -e | |
| # Escape special characters for Slack mrkdwn | |
| escape_slack() { | |
| local s="$1" | |
| s="${s//&/&}" | |
| s="${s//</<}" | |
| s="${s//>/>}" | |
| echo "$s" | |
| } | |
| # State to emoji mapping | |
| state_emoji() { | |
| local state="$1" | |
| case "$state" in | |
| draft) | |
| echo ":pr-draft:" | |
| ;; | |
| merged) | |
| echo ":merge:" | |
| ;; | |
| closed) | |
| echo ":closed:" | |
| ;; | |
| open|*) | |
| echo ":pr:" | |
| ;; | |
| esac | |
| } | |
| # Parse GitHub URL to extract owner, repo, type (pull/issues), and number | |
| parse_github_url() { | |
| local url="$1" | |
| # Remove trailing slash if present | |
| url="${url%/}" | |
| # Match patterns like: | |
| # https://github.com/owner/repo/pull/123 | |
| # https://github.com/owner/repo/issues/123 | |
| if [[ "$url" =~ github\.com/([^/]+)/([^/]+)/(pull|issues)/([0-9]+) ]]; then | |
| OWNER="${BASH_REMATCH[1]}" | |
| REPO="${BASH_REMATCH[2]}" | |
| TYPE="${BASH_REMATCH[3]}" | |
| NUMBER="${BASH_REMATCH[4]}" | |
| return 0 | |
| fi | |
| return 1 | |
| } | |
| # Fetch PR info using gh CLI (returns tab-separated: title, state, isDraft) | |
| fetch_pr_info() { | |
| local owner="$1" | |
| local repo="$2" | |
| local number="$3" | |
| gh pr view "$number" --repo "$owner/$repo" --json title,state,isDraft --jq '[.title, .state, .isDraft] | @tsv' 2>/dev/null | |
| } | |
| # Fetch issue info using gh CLI (returns tab-separated: title, state) | |
| fetch_issue_info() { | |
| local owner="$1" | |
| local repo="$2" | |
| local number="$3" | |
| gh issue view "$number" --repo "$owner/$repo" --json title,state --jq '[.title, .state] | @tsv' 2>/dev/null | |
| } | |
| # Main function | |
| main() { | |
| local url="$1" | |
| if [[ -z "$url" ]]; then | |
| echo "Usage: gh-slack <github-pr-or-issue-url>" >&2 | |
| echo "" >&2 | |
| echo "Examples:" >&2 | |
| echo " gh-slack https://github.com/owner/repo/pull/123" >&2 | |
| echo " gh-slack https://github.com/owner/repo/issues/456" >&2 | |
| exit 1 | |
| fi | |
| # Check if gh CLI is available | |
| if ! command -v gh &> /dev/null; then | |
| echo "Error: gh CLI is not installed. Please install it first:" >&2 | |
| echo " brew install gh" >&2 | |
| exit 1 | |
| fi | |
| # Parse the URL | |
| if ! parse_github_url "$url"; then | |
| echo "Error: Invalid GitHub URL. Expected format:" >&2 | |
| echo " https://github.com/owner/repo/pull/123" >&2 | |
| echo " https://github.com/owner/repo/issues/123" >&2 | |
| exit 1 | |
| fi | |
| local title="" | |
| local state="" | |
| local emoji="" | |
| if [[ "$TYPE" == "pull" ]]; then | |
| # Fetch PR info (tab-separated: title, state, isDraft) | |
| local pr_info | |
| pr_info=$(fetch_pr_info "$OWNER" "$REPO" "$NUMBER") | |
| if [[ -z "$pr_info" ]]; then | |
| echo "Error: Could not fetch PR #$NUMBER from $OWNER/$REPO" >&2 | |
| exit 1 | |
| fi | |
| local pr_state is_draft | |
| IFS=$'\t' read -r title pr_state is_draft <<< "$pr_info" | |
| # Determine state | |
| if [[ "$is_draft" == "true" ]]; then | |
| state="draft" | |
| elif [[ "$pr_state" == "MERGED" ]]; then | |
| state="merged" | |
| elif [[ "$pr_state" == "CLOSED" ]]; then | |
| state="closed" | |
| else | |
| state="open" | |
| fi | |
| else | |
| # Fetch issue info (tab-separated: title, state) | |
| local issue_info | |
| issue_info=$(fetch_issue_info "$OWNER" "$REPO" "$NUMBER") | |
| if [[ -z "$issue_info" ]]; then | |
| echo "Error: Could not fetch issue #$NUMBER from $OWNER/$REPO" >&2 | |
| exit 1 | |
| fi | |
| local issue_state | |
| IFS=$'\t' read -r title issue_state <<< "$issue_info" | |
| if [[ "$issue_state" == "CLOSED" ]]; then | |
| state="closed" | |
| else | |
| state="open" | |
| fi | |
| fi | |
| # Get emoji for state | |
| emoji=$(state_emoji "$state") | |
| # Escape title for HTML | |
| local escaped_title | |
| escaped_title=$(escape_slack "$title") | |
| # Generate HTML link (for rich text clipboard) | |
| local html_link="$emoji <a href=\"$url\">$escaped_title</a>" | |
| # Generate plain text fallback | |
| local plain_link="$emoji $title — $url" | |
| # Output the plain version | |
| echo "$plain_link" | |
| # Try to copy rich text (HTML) to clipboard on macOS | |
| if [[ "$(uname)" == "Darwin" ]]; then | |
| # Use hexdump approach for reliable HTML clipboard on macOS | |
| local hex_html | |
| hex_html=$(echo -n "$html_link" | hexdump -ve '1/1 "%.2x"') | |
| osascript <<EOF | |
| set the clipboard to {«class HTML»:«data HTML${hex_html}», string:"${plain_link//\"/\\\"}"} | |
| EOF | |
| if [[ $? -ne 0 ]]; then | |
| echo -n "$plain_link" | pbcopy | |
| fi | |
| elif command -v xclip &> /dev/null; then | |
| echo -n "$plain_link" | xclip -selection clipboard | |
| fi | |
| } | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment