Last active
February 18, 2026 11:12
-
-
Save yossuli/b27b64ebdac4507948240ee09d1e4bcd to your computer and use it in GitHub Desktop.
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 | |
| export LANG=ja_JP.UTF-8 | |
| export LC_ALL=ja_JP.UTF-8 | |
| HISTORY_FILE="$HOME/.clipboard_history" | |
| CONFIG_FILE="$HOME/.config/clipboard-manager/config" | |
| # 設定ファイルの読み込み(デフォルト値) | |
| NOTIFY_ON_PASTE=true | |
| NOTIFY_ON_ERROR=true | |
| # 設定ファイルが存在すれば読み込む | |
| if [[ -f "$CONFIG_FILE" ]]; then | |
| # shellcheck source=/dev/null | |
| source "$CONFIG_FILE" | |
| fi | |
| check_history() { | |
| if [[ ! -f "$HISTORY_FILE" ]] || [[ ! -s "$HISTORY_FILE" ]]; then | |
| osascript -e 'display dialog "クリップボード履歴が空です" buttons {"OK"} default button 1' 2>/dev/null | |
| exit 0 | |
| fi | |
| } | |
| # プレビューデータ生成とAppleScriptリスト変換 | |
| build_preview_list() { | |
| PREVIEW_DATA=$(tail -r "$HISTORY_FILE" | /usr/bin/jq -r '(.timestamp[11:16]) + " - " + (.content | gsub("\n"; "↵") | .[0:60])' 2>/dev/null) || true | |
| if [[ -z "$PREVIEW_DATA" ]]; then | |
| osascript -e 'display dialog "クリップボード履歴が空です" buttons {"OK"} default button 1' 2>/dev/null | |
| exit 0 | |
| fi | |
| AS_LIST="" | |
| LIST_ITEM_COUNT=0 | |
| while IFS= read -r line; do | |
| [[ -z "$line" ]] && continue | |
| ESCAPED=${line//\\/\\\\} | |
| ESCAPED=${ESCAPED//\"/\\\"} | |
| if [[ -n "$AS_LIST" ]]; then | |
| AS_LIST="${AS_LIST}, \"${ESCAPED}\"" | |
| else | |
| AS_LIST="\"${ESCAPED}\"" | |
| fi | |
| LIST_ITEM_COUNT=$((LIST_ITEM_COUNT + 1)) | |
| done <<< "$PREVIEW_DATA" | |
| } | |
| # --- 編集モード --- | |
| run_edit() { | |
| check_history | |
| ENTRY_COUNT=$(wc -l < "$HISTORY_FILE" | tr -d ' ') | |
| build_preview_list | |
| # 末尾に全クリアオプション | |
| EDIT_LIST="${AS_LIST}, \"--- 全履歴をクリア ---\"" | |
| SELECTED_INDICES=$(osascript 2>/dev/null <<EOF | |
| set historyList to {${EDIT_LIST}} | |
| set selectedItems to choose from list historyList with prompt "削除するエントリを選択(複数選択可):" with title "Clipboard History - 編集" with multiple selections allowed | |
| if selectedItems is false then | |
| return "-1" | |
| end if | |
| set indexList to "" | |
| repeat with sel in selectedItems | |
| repeat with i from 1 to count of historyList | |
| if item i of historyList is (sel as text) then | |
| if indexList is "" then | |
| set indexList to ((i - 1) as text) | |
| else | |
| set indexList to indexList & "," & ((i - 1) as text) | |
| end if | |
| exit repeat | |
| end if | |
| end repeat | |
| end repeat | |
| return indexList | |
| EOF | |
| ) || true | |
| [[ -z "$SELECTED_INDICES" || "$SELECTED_INDICES" == "-1" ]] && return | |
| # 全クリアが含まれているかチェック | |
| if echo ",$SELECTED_INDICES," | grep -q ",$LIST_ITEM_COUNT,"; then | |
| CONFIRM=$(osascript 2>/dev/null <<EOF | |
| display dialog "全てのクリップボード履歴を削除しますか?" buttons {"キャンセル", "全削除"} default button "キャンセル" with icon caution with title "Clipboard History" | |
| return button returned of result | |
| EOF | |
| ) || true | |
| if [[ "$CONFIRM" == "全削除" ]]; then | |
| : > "$HISTORY_FILE" | |
| osascript -e 'display notification "全履歴を削除しました" with title "Clipboard History"' 2>/dev/null | |
| fi | |
| return | |
| fi | |
| # 選択インデックスを元ファイルの行番号に変換して削除 | |
| LINES_TO_DELETE="" | |
| IFS=',' read -ra INDICES <<< "$SELECTED_INDICES" | |
| for idx in "${INDICES[@]}"; do | |
| ORIGINAL_LINE=$(( ENTRY_COUNT - idx )) | |
| if [[ -n "$LINES_TO_DELETE" ]]; then | |
| LINES_TO_DELETE="${LINES_TO_DELETE},${ORIGINAL_LINE}" | |
| else | |
| LINES_TO_DELETE="${ORIGINAL_LINE}" | |
| fi | |
| done | |
| awk -v lines="$LINES_TO_DELETE" 'BEGIN{split(lines,a,","); for(i in a) skip[a[i]]=1} !(NR in skip)' "$HISTORY_FILE" > "$HISTORY_FILE.tmp" | |
| mv "$HISTORY_FILE.tmp" "$HISTORY_FILE" | |
| DELETE_COUNT=${#INDICES[@]} | |
| osascript -e "display notification \"${DELETE_COUNT}件のエントリを削除しました\" with title \"Clipboard History\"" 2>/dev/null | |
| } | |
| # --- 設定モード --- | |
| run_settings() { | |
| while true; do | |
| # 現在の設定を表示用に変換 | |
| get_status_text() { | |
| if [[ "$1" == "true" ]]; then | |
| echo "ON" | |
| else | |
| echo "OFF" | |
| fi | |
| } | |
| PASTE_STATUS=$(get_status_text "$NOTIFY_ON_PASTE") | |
| ERROR_STATUS=$(get_status_text "$NOTIFY_ON_ERROR") | |
| # アクション文言を動的に生成 | |
| if [[ "$NOTIFY_ON_PASTE" == "true" ]]; then | |
| PASTE_ACTION="ペースト通知をOFFにする" | |
| else | |
| PASTE_ACTION="ペースト通知をONにする" | |
| fi | |
| if [[ "$NOTIFY_ON_ERROR" == "true" ]]; then | |
| ERROR_ACTION="エラー通知をOFFにする" | |
| else | |
| ERROR_ACTION="エラー通知をONにする" | |
| fi | |
| # 設定メニューを表示 | |
| SETTINGS_RESULT=$(osascript 2>/dev/null <<EOF | |
| set currentSettings to "【現在の設定】 | |
| ペースト成功時: $PASTE_STATUS | |
| エラー時: $ERROR_STATUS" | |
| set actionList to {"$PASTE_ACTION", "$ERROR_ACTION", "戻る"} | |
| set selectedAction to choose from list actionList with prompt currentSettings with title "Clipboard Manager - 通知設定" | |
| if selectedAction is false then | |
| return "CANCEL" | |
| else | |
| return item 1 of selectedAction | |
| end if | |
| EOF | |
| ) || echo "CANCEL" | |
| # 「戻る」「キャンセル」の場合はループを抜ける | |
| if [[ "$SETTINGS_RESULT" == "戻る" ]] || [[ "$SETTINGS_RESULT" == "CANCEL" ]]; then | |
| return | |
| fi | |
| # ユーザーの選択に応じて設定を変更 | |
| case "$SETTINGS_RESULT" in | |
| "ペースト通知をONにする") | |
| NOTIFY_ON_PASTE=true | |
| MESSAGE="ペースト通知: ON" | |
| ;; | |
| "ペースト通知をOFFにする") | |
| NOTIFY_ON_PASTE=false | |
| MESSAGE="ペースト通知: OFF" | |
| ;; | |
| "エラー通知をONにする") | |
| NOTIFY_ON_ERROR=true | |
| MESSAGE="エラー通知: ON" | |
| ;; | |
| "エラー通知をOFFにする") | |
| NOTIFY_ON_ERROR=false | |
| MESSAGE="エラー通知: OFF" | |
| ;; | |
| esac | |
| # 設定ファイルに保存 | |
| cat > "$CONFIG_FILE" <<CONFIGEOF | |
| # Clipboard Manager 設定ファイル | |
| # ペースト時の通知を表示するか(true/false) | |
| NOTIFY_ON_PASTE=$NOTIFY_ON_PASTE | |
| # エラー時の通知を表示するか(true/false) | |
| NOTIFY_ON_ERROR=$NOTIFY_ON_ERROR | |
| CONFIGEOF | |
| # 結果を通知 | |
| osascript -e "display notification \"$MESSAGE\" with title \"設定を保存しました\"" 2>/dev/null | |
| # ループ継続(UIを再表示して最新の状態を見せる) | |
| done | |
| } | |
| # --- メインモード --- | |
| while true; do | |
| check_history | |
| ENTRY_COUNT=$(wc -l < "$HISTORY_FILE" | tr -d ' ') | |
| build_preview_list | |
| # 末尾に編集・設定オプション | |
| AS_LIST="${AS_LIST}, \"--- クリップボード履歴を編集 ---\", \"--- 通知設定 ---\"" | |
| INDEX=$(osascript 2>/dev/null <<EOF | |
| set historyList to {${AS_LIST}} | |
| set selectedItem to choose from list historyList with prompt "クリップボードエントリを選択:" with title "Clipboard History" default items {item 1 of historyList} | |
| if selectedItem is false then | |
| return "-1" | |
| end if | |
| set selectedText to item 1 of selectedItem | |
| repeat with i from 1 to count of historyList | |
| if item i of historyList is selectedText then | |
| return ((i - 1) as text) | |
| end if | |
| end repeat | |
| return "-1" | |
| EOF | |
| ) || true | |
| [[ -z "$INDEX" || "$INDEX" == "-1" ]] && exit 0 | |
| # 編集モードが選択された場合 | |
| if (( INDEX == LIST_ITEM_COUNT )); then | |
| run_edit | |
| continue | |
| fi | |
| # 設定モードが選択された場合 | |
| if (( INDEX == LIST_ITEM_COUNT + 1 )); then | |
| run_settings | |
| continue | |
| fi | |
| # 通常選択:クリップボードに復元 | |
| break | |
| done | |
| # 通常選択の処理 | |
| ERROR_LOG="$HOME/.clipboard_pick_error.log" | |
| JQ_RESULT=$(tail -r "$HISTORY_FILE" | /usr/bin/jq -s ".[$INDEX].content // empty" -r 2>>"$ERROR_LOG") | |
| if [[ -z "$JQ_RESULT" ]]; then | |
| echo "[$(date '+%Y-%m-%d %H:%M:%S')] jq結果が空 INDEX=$INDEX ENTRY_COUNT=$ENTRY_COUNT" >> "$ERROR_LOG" | |
| if [[ "$NOTIFY_ON_ERROR" == "true" ]]; then | |
| osascript -e 'display notification "エラー: エントリ取得失敗。~/.clipboard_pick_error.log を確認" with title "Clipboard History"' 2>/dev/null | |
| fi | |
| exit 1 | |
| fi | |
| # クリップボードに復元 | |
| if ! echo -n "$JQ_RESULT" | pbcopy 2>>"$ERROR_LOG"; then | |
| echo "[$(date '+%Y-%m-%d %H:%M:%S')] pbcopy失敗" >> "$ERROR_LOG" | |
| if [[ "$NOTIFY_ON_ERROR" == "true" ]]; then | |
| osascript -e 'display notification "エラー: クリップボードへのコピー失敗" with title "Clipboard History"' 2>/dev/null | |
| fi | |
| exit 1 | |
| fi | |
| # AppleScriptでcmd+vを送信 | |
| APPLESCRIPT_OUTPUT=$(osascript 2>&1 <<'APPLESCRIPT_EOF' | |
| try | |
| tell application "System Events" | |
| delay 0.15 | |
| keystroke "v" using command down | |
| end tell | |
| return "SUCCESS" | |
| on error errMsg number errNum | |
| return "ERROR:" & errNum & ":" & errMsg | |
| end try | |
| APPLESCRIPT_EOF | |
| ) | |
| APPLESCRIPT_EXIT_CODE=$? | |
| # 結果に応じた通知 | |
| if [[ $APPLESCRIPT_EXIT_CODE -eq 0 ]] && [[ "$APPLESCRIPT_OUTPUT" == "SUCCESS" ]]; then | |
| if [[ "$NOTIFY_ON_PASTE" == "true" ]]; then | |
| osascript -e 'display notification "ペーストしました" with title "Clipboard History"' 2>/dev/null | |
| fi | |
| else | |
| if [[ "$NOTIFY_ON_ERROR" == "true" ]]; then | |
| osascript -e 'display notification "クリップボードに復元しました。⌘Vでペーストしてください(アクセシビリティ権限が必要な可能性があります)" with title "Clipboard History"' 2>/dev/null | |
| fi | |
| fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment