# [メタ情報] # 識別子: ChatGPT連携_要約保存Mac用スクリプト_exe # システム名: ChatGPT連携_要約保存Mac用スクリプト # 技術種別: Misc # 機能名: Misc # 使用言語: AppleScript # 状態: 実行用 # [/メタ情報] 要約:このAppleScriptは、クリップボード内のテキストをChatGPT要約付きまたは手入力タイトル付きでテキストファイルに保存する自動化ツールである。実行時にまずクリップボード内容を取得し、空なら警告を表示。冒頭約120文字をプレビューして保存可否を確認する。次に要約を付けるかを尋ね、「はい」を選ぶとChatGPTアプリを自動起動し、要約プロンプトを貼り付けて送信。ユーザーが要約をコピーすると検証して採用する。タイトルは要約または本文冒頭行から自動生成し、無効時は「無題」とする。「いいえ」を選んだ場合はタイトルを手入力する。保存時は日時付きのファイル名(YYYYMMDD-HHMMSS_タイトル) (* ChatGPTデスクトップ連携:クリップボード →(ChatGPT要約 or 手入力)→ txt保存 - 保存先: /Volumes/NO3_SSD/Dropbox/dropbox_1/ChatGPTによる要約と本文/ - ファイル名: YYYYMMDD-HHMMSS_タイトル.txt *) -- ===== 設定 ===== property SAVE_DIR : "/Volumes/NO3_SSD/Dropbox/dropbox_1/ChatGPTによる要約と本文/" property MAX_SUMMARY_CHARS : 300 property TITLE_MAX_CHARS : 40 property PROMPT_HEAD : "以下のテキストを日本語で" & (MAX_SUMMARY_CHARS as string) & "字以内に要約してください。出力は本文のみ(接頭辞や記号なし)。" & return & return -- ================= on run -- 1) クリップボード取得 set clipText to my getClipboardText() if clipText is missing value or (length of clipText) = 0 then display alert "クリップボードにテキストがありません" buttons {"OK"} as critical return end if -- 2) 保存確認(プレビュー) set previewText to text 1 thru (my minInt(120, length of clipText)) of clipText set dlg to display dialog "この内容を保存しますか?" & return & return & previewText buttons {"キャンセル", "続行"} default button "続行" if button returned of dlg is "キャンセル" then return -- 3) 要約の要否 set q to display dialog "ChatGPTによる要約を挿入しますか?" buttons {"いいえ", "はい"} default button "はい" set summaryText to "" set userTitle to "" set firstLine to item 1 of my splitByNewline(clipText) if button returned of q is "いいえ" then -- 要約なし:タイトルを手入力 set baseTitle to my clampLen(my trimText(firstLine), TITLE_MAX_CHARS) if baseTitle = "" then set baseTitle to "無題" set titleDlg to display dialog "タイトル(ファイル名にも使います)" default answer baseTitle buttons {"キャンセル", "OK"} default button "OK" if button returned of titleDlg is "キャンセル" then return set userTitle to my sanitizeFilename(text returned of titleDlg) if userTitle = "" then set userTitle to "無題" else -- 要約あり:ChatGPTに投げ、要約をクリップボードから取得(検証つき) my bringUpChatGPTAndAsk(clipText) -- 送信後にクリップボードは即復元される repeat set w to display dialog "ChatGPTの返信が出たら、返信テキストだけを選択して ⌘C でコピーしてください。" buttons {"中止", "取得"} default button "取得" if button returned of w is "中止" then return set summaryText to my getClipboardText() set summaryText to my clampLen(my trimText(summaryText), MAX_SUMMARY_CHARS) -- 無効(空 or プロンプト混入)のときは再試行 if summaryText is "" or my looksLikePrompt(summaryText) or my containsLongChunkOf(summaryText, clipText) then display dialog "要約が取得できていません。返信本文のみを再度コピーしてください。" buttons {"再試行"} default button "再試行" else exit repeat end if end repeat -- タイトル自動生成(要約→本文1行目→無題) set autoTitleBase to my clampLen(my trimText(summaryText), TITLE_MAX_CHARS) if autoTitleBase = "" then set autoTitleBase to my clampLen(my trimText(firstLine), TITLE_MAX_CHARS) if autoTitleBase = "" then set autoTitleBase to "無題" set userTitle to my sanitizeFilename(autoTitleBase) end if -- 4) 保存 my ensureDir(SAVE_DIR) set tsFile to do shell script "date +%Y%m%d-%H%M%S" set tsHuman to do shell script "date '+%Y-%m-%d %H:%M:%S'" set filename to tsFile & "_" & userTitle & ".txt" set fullpath to SAVE_DIR & filename set outText to "[" & tsHuman & "]" & return if summaryText is not "" then set outText to outText & "要約:" & summaryText & return & return end if set outText to outText & clipText my writeTextUTF8(fullpath, outText) display notification filename with title "保存しました" subtitle SAVE_DIR end run -- ===== ChatGPT(デスクトップアプリ)へ投稿(クリップボードは復元)===== -- ChatGPT の入力欄を確実にフォーカスして貼付・送信(クリップボードは復元) on bringUpChatGPTAndAsk(bodyText) set postText to PROMPT_HEAD & bodyText set oldClip to my getClipboardText() tell application "ChatGPT" to activate delay 0.5 tell application "System Events" if not (exists process "ChatGPT") then return tell process "ChatGPT" set frontmost to true -- 新規チャット(不要なら失敗してもOK) try keystroke "n" using {command down} end try delay 0.3 -- 中央の入力欄(Text Area)にフォーカス -- UI 階層が環境で少し違うことがあるので順に当てに行く set tArea to missing value repeat with p in {¬ "text area 1 of group 1 of window 1", ¬ "text area 1 of scroll area 1 of group 1 of window 1", ¬ "text area 1 of window 1"} try set tArea to (run script "tell application \"System Events\" to tell process \"ChatGPT\" to " & p) exit repeat end try end repeat if tArea is not missing value then try set focused of tArea to true end try else -- フォールバック:ウィンドウ内をクリック→TABで移動 key code 125 -- ↓ delay 0.1 key code 48 -- Tab end if delay 0.2 -- 貼付→送信 set the clipboard to postText keystroke "v" using {command down} delay 0.2 key code 36 -- Return end tell end tell -- クリップボードを復元 if oldClip is not missing value then set the clipboard to oldClip end bringUpChatGPTAndAsk -- ===== ヘルパ ===== on getClipboardText() try set t to the clipboard as text return t on error return missing value end try end getClipboardText on ensureDir(d) do shell script "mkdir -p " & quoted form of d end ensureDir -- UTF-8で安全に書き出す(Python経由) on writeTextUTF8(p, t) set sh to "python3 - <<'PY' import pathlib p = " & quoted form of p & " t = " & my pyTriple(t) & " pathlib.Path(p).write_text(t, encoding='utf-8') PY" do shell script sh end writeTextUTF8 on splitByNewline(s) set AppleScript's text item delimiters to {return, linefeed} set arr to text items of s set AppleScript's text item delimiters to "" return arr end splitByNewline on trimText(s) do shell script "python3 - <<'PY' s=" & my pyTriple(s) & " print(s.strip()) PY" end trimText on clampLen(s, n) if (length of s) ≤ n then return s return text 1 thru n of s end clampLen on sanitizeFilename(s) set s to my trimText(s) set AppleScript's text item delimiters to "" set out to "" repeat with ch in characters of s set c to contents of ch if c is in "/:\\*?\"<>| " then set out to out & "_" else set out to out & c end if end repeat if out = "" then set out to "無題" return out end sanitizeFilename on minInt(a, b) if a < b then return a return b end minInt on looksLikePrompt(s) -- プロンプトの先頭語句に似ている場合は true return s starts with "以下のテキストを日本語で" end looksLikePrompt on containsLongChunkOf(a, b) -- a が b の長い断片(40文字以上)を含んでいれば true(誤取得の疑い) if (length of b) < 40 then return false set needle to text 1 thru 40 of b return a contains needle end containsLongChunkOf on pyTriple(s) set s to my replaceText(s, "\\", "\\\\") set s to my replaceText(s, "\"\"\"", "\\\"\\\"\\\"") return "\"\"\"" & s & "\"\"\"" end pyTriple on replaceText(s, f, r) set AppleScript's text item delimiters to f set parts to text items of s set AppleScript's text item delimiters to r set out to parts as text set AppleScript's text item delimiters to "" return out end replaceText