# [メタ情報] # 識別子: geminiを使った日本語要約_exe # システム名: 未分類 # 技術種別: Misc # 機能名: Misc # 使用言語: AppleScript # 状態: 公開用 # [/メタ情報] 要約: このAppleScriptは、Google Gemini APIを利用し、クリップボード内のテキストを自動要約して保存するツールです。 まず、`.env`ファイルからGemini APIキーと保存ディレクトリを読み込み、未設定であれば警告表示後に処理を停止します。 次に、クリップボードからテキストを取得し、内容をプレビュー表示してユーザーに確認を促します。 確認後、スクリプトはGemini APIに接続し、利用可能なモデルから最適なものを自動選択して要約を生成します。API通信には最大3回のリトライ機構が備わっており、堅牢性を高めています。 要約処理が完了すると、要約文と元の本文を組み合わせ、タイムスタンプと要約の冒頭部分から生成したファイル名で、指定されたディレクトリ(存在しない場合は自動作成)にUTF-8形式で安全に保存します。 本スクリプトは、Pythonを介したAPI通信や、ファイル操作、エラーハンドリングなどのユーティリティ関数も備えています。 M2Mac用 <任意のフォルダパス>/gemini要約.workflow/ gemini要約.workflow Services Automator ワークフローが受け取る項目:テキスト 選択肢:すべてのアプリケーション ``` -- ===== 設定 ===== property MAX_SUMMARY_CHARS : 400 property TITLE_MAX_CHARS : 40 -- ================= on run -- 1) ポータブル金庫(.env)からの環境変数動的ロード set currentUser to do shell script "whoami" set envPath to "/Users/" & currentUser & "/python_scripts/.env" set myApiKey to my getEnvVar("GEMINI_API_KEY", envPath) set saveDir to my getEnvVar("GEMINI_SAVE_DIR", envPath) -- 🛡️ 【自律停止ガード】必要な環境変数が不足している場合はダイアログ警告を出して安全に自律停止 if myApiKey is "" or saveDir is "" then display alert "❌ [gemini要約] 金庫(.env)に必要な GEMINI_API_KEY または GEMINI_SAVE_DIR が設定されていません。安全に自律停止します。" as critical return end if -- 2) クリップボードからテキスト取得 set clipText to my getClipboardText() if clipText is missing value or (length of clipText) = 0 then display alert "クリップボードにテキストがありません" return end if set previewText to text 1 thru (my minInt(120, length of clipText)) of clipText set dlg to display dialog "この内容をGeminiで保存しますか(リトライ3回)?" & return & return & previewText buttons {"キャンセル", "続行"} default button "続行" if button returned of dlg is "キャンセル" then return display notification "利用可能なモデルを自動選択して要約中..." with title "通信開始" -- 3) GeminiへのAPI問い合わせの実行 set finalResult to my askGemini(clipText, myApiKey) if finalResult starts with "Error:" then display alert "Gemini APIエラー" message finalResult return end if -- 4) ディレクトリの存在確認(なければ自動生成)と安全な書き込み my ensureDir(saveDir) 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 firstLine to item 1 of my splitByNewline(finalResult) set userTitle to my sanitizeFilename(my clampLen(firstLine, TITLE_MAX_CHARS)) set filename to tsFile & "_" & userTitle & ".txt" my writeTextUTF8(saveDir & filename, "[" & tsHuman & "]" & return & return & finalResult) display notification filename with title "保存完了" end run -- 🔁 .envから特定のキーを読み込むサブ関数 on getEnvVar(varName, envFilePath) try set cmd to "grep '^" & varName & "=' " & quoted form of envFilePath & " | cut -d'=' -f2- | tr -d '\"' | tr -d \"'\"" set val to do shell script cmd return val on error return "" end try end getEnvVar -- 📡 Gemini APIとの通信および最新モデルの自動選択 on askGemini(targetText, apiKeyVal) set promptText to "以下のテキストを日本語で" & (MAX_SUMMARY_CHARS as string) & "字以内に要約してください。出力は要約文のみ。その後に続けて元の本文も出力してください。" & return & return set fullInput to promptText & targetText set tempInputPath to "/tmp/gemini_in.txt" try my writeTextUTF8(tempInputPath, fullInput) -- Pythonでモデル一覧をAPI経由で動的取得し、最適なgenerateContent対応モデルを選んで実行する set pyCode to "import json, sys, subprocess, time try: # 1. 利用可能なモデルをリストから動的取得 list_url = 'https://generativelanguage.googleapis.com/v1beta/models?key=" & apiKeyVal & "' list_res = subprocess.run(['curl', '-s', list_url], capture_output=True, text=True) models_data = json.loads(list_res.stdout) # generateContentをサポートしている有効なモデルを自動選択 chosen_model = next((m['name'] for m in models_data.get('models', []) if 'generateContent' in m.get('supportedGenerationMethods', [])), None) if not chosen_model: print('Error: 使用可能なモデルが見つかりません。Google AI StudioでAPIの有効化を確認してください。') sys.exit(1) # 2. 選んだモデルで実行(高負荷エラー対策のリトライループを実装) with open('" & tempInputPath & "', 'r', encoding='utf-8') as f: content = f.read() payload = json.dumps({'contents': [{'parts': [{'text': content}]}]}) exec_url = f'https://generativelanguage.googleapis.com/v1beta/{chosen_model}:generateContent?key=" & apiKeyVal & "' max_retries = 3 retry_delay = 3 for attempt in range(1, max_retries + 1): # API通信を実行 cmd = ['curl', '-s', '-X', 'POST', exec_url, '-H', 'Content-Type: application/json', '-d', payload] res = subprocess.run(cmd, capture_output=True, text=True) try: res_data = json.loads(res.stdout) # 正常なテキスト応答の取り出し ans = res_data['candidates'][0]['content']['parts'][0]['text'] print(ans) sys.exit(0) except Exception as parse_err: err_msg = res_data.get('error', {}).get('message', 'Unknown Error') if attempt == max_retries: print('Error: ' + err_msg) sys.exit(1) time.sleep(retry_delay) retry_delay *= 2 continue except Exception as e: print('Error: ' + str(e)) sys.exit(1) " -- macOSシステム保護(-1721)対策のため環境のpython3を安全に稼働 return do shell script "/usr/bin/env python3 -c " & quoted form of pyCode on error errMsg return "Error: 通信失敗 " & errMsg end try end askGemini -- 🛠️ ユーティリティ群(1文字の抜けもなく現状維持を徹底) on getClipboardText() try return the clipboard as text on error return missing value end try end getClipboardText on ensureDir(d) do shell script "mkdir -p " & quoted form of d end ensureDir on writeTextUTF8(p, t) try set fRef to open for access (POSIX file p) with write permission set eof of fRef to 0 write t to fRef as «class utf8» close access fRef on error try close access (POSIX file p) end try -- 頑丈なフォールバックとしてのpythonワンライナー書き出し set pyWrite to "import sys; import codecs; f = codecs.open(sys.argv[1], 'w', 'utf-8'); f.write(sys.argv[2]); f.close()" do shell script "python3 -c " & quoted form of pyWrite & " " & quoted form of p & " " & quoted form of t end try end writeTextUTF8 on splitByNewline(txt) set {oldTIDs, AppleScript's text item delimiters} to {AppleScript's text item delimiters, {return, linefeed}} set txtItems to text items of txt set AppleScript's text item delimiters to oldTIDs return txtItems end splitByNewline on sanitizeFilename(s) set illegal to "/:\\*?\"<>| " & linefeed & return set out to "" repeat with ch in characters of s if contents of ch is in illegal then set out to out & "_" else set out to out & ch end if end repeat return out end sanitizeFilename on clampLen(s, n) if (length of s) ≤ n then return s return text 1 thru n of s end clampLen on minInt(a, b) if a < b then return a return b end minInt ``` M2Mac /Applications/自作appleスクリプト/gemini要約.scpt ``` -- ===== 設定 ===== property MAX_SUMMARY_CHARS : 400 property TITLE_MAX_CHARS : 40 -- ================= on run -- 1) ポータブル金庫(.env)からの環境変数動的ロード set currentUser to do shell script "whoami" set envPath to "/Users/" & currentUser & "/python_scripts/.env" set myApiKey to my getEnvVar("GEMINI_API_KEY", envPath) set saveDir to my getEnvVar("GEMINI_SAVE_DIR", envPath) -- 🛡️ 【自律停止ガード】必要な環境変数が不足している場合はダイアログ警告を出して安全に自律停止 if myApiKey is "" or saveDir is "" then display alert "❌ [gemini要約] 金庫(.env)に必要な GEMINI_API_KEY または GEMINI_SAVE_DIR が設定されていません。安全に自律停止します。" as critical return end if -- 2) クリップボードからテキスト取得 set clipText to my getClipboardText() if clipText is missing value or (length of clipText) = 0 then display alert "クリップボードにテキストがありません" return end if set previewText to text 1 thru (my minInt(120, length of clipText)) of clipText set dlg to display dialog "この内容をGeminiで保存しますか(リトライ3回)?" & return & return & previewText buttons {"キャンセル", "続行"} default button "続行" if button returned of dlg is "キャンセル" then return display notification "利用可能なモデルを自動選択して要約中..." with title "通信開始" -- 3) GeminiへのAPI問い合わせの実行 set finalResult to my askGemini(clipText, myApiKey) if finalResult starts with "Error:" then display alert "Gemini APIエラー" message finalResult return end if -- 4) ディレクトリの存在確認(なければ自動生成)と安全な書き込み my ensureDir(saveDir) 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 firstLine to item 1 of my splitByNewline(finalResult) set userTitle to my sanitizeFilename(my clampLen(firstLine, TITLE_MAX_CHARS)) set filename to tsFile & "_" & userTitle & ".txt" my writeTextUTF8(saveDir & filename, "[" & tsHuman & "]" & return & return & finalResult) display notification filename with title "保存完了" end run -- 🔁 .envから特定のキーを読み込むサブ関数 on getEnvVar(varName, envFilePath) try set cmd to "grep '^" & varName & "=' " & quoted form of envFilePath & " | cut -d'=' -f2- | tr -d '\"' | tr -d \"'\"" set val to do shell script cmd return val on error return "" end try end getEnvVar -- 📡 Gemini APIとの通信および最新モデルの自動選択 on askGemini(targetText, apiKeyVal) set promptText to "以下のテキストを日本語で" & (MAX_SUMMARY_CHARS as string) & "字以内に要約してください。出力は要約文のみ。その後に続けて元の本文も出力してください。" & return & return set fullInput to promptText & targetText set tempInputPath to "/tmp/gemini_in.txt" try my writeTextUTF8(tempInputPath, fullInput) -- Pythonでモデル一覧をAPI経由で動的取得し、最適なgenerateContent対応モデルを選んで実行する set pyCode to "import json, sys, subprocess, time try: # 1. 利用可能なモデルをリストから動的取得 list_url = 'https://generativelanguage.googleapis.com/v1beta/models?key=" & apiKeyVal & "' list_res = subprocess.run(['curl', '-s', list_url], capture_output=True, text=True) models_data = json.loads(list_res.stdout) # generateContentをサポートしている有効なモデルを自動選択 chosen_model = next((m['name'] for m in models_data.get('models', []) if 'generateContent' in m.get('supportedGenerationMethods', [])), None) if not chosen_model: print('Error: 使用可能なモデルが見つかりません。Google AI StudioでAPIの有効化を確認してください。') sys.exit(1) # 2. 選んだモデルで実行(高負荷エラー対策のリトライループを実装) with open('" & tempInputPath & "', 'r', encoding='utf-8') as f: content = f.read() payload = json.dumps({'contents': [{'parts': [{'text': content}]}]}) exec_url = f'https://generativelanguage.googleapis.com/v1beta/{chosen_model}:generateContent?key=" & apiKeyVal & "' max_retries = 3 retry_delay = 3 for attempt in range(1, max_retries + 1): # API通信を実行 cmd = ['curl', '-s', '-X', 'POST', exec_url, '-H', 'Content-Type: application/json', '-d', payload] res = subprocess.run(cmd, capture_output=True, text=True) try: res_data = json.loads(res.stdout) # 正常なテキスト応答の取り出し ans = res_data['candidates'][0]['content']['parts'][0]['text'] print(ans) sys.exit(0) except Exception as parse_err: err_msg = res_data.get('error', {}).get('message', 'Unknown Error') if attempt == max_retries: print('Error: ' + err_msg) sys.exit(1) time.sleep(retry_delay) retry_delay *= 2 continue except Exception as e: print('Error: ' + str(e)) sys.exit(1) " -- macOSシステム保護(-1721)対策のため環境のpython3を安全に稼働 return do shell script "/usr/bin/env python3 -c " & quoted form of pyCode on error errMsg return "Error: 通信失敗 " & errMsg end try end askGemini -- 🛠️ ユーティリティ群(1文字の抜けもなく現状維持を徹底) on getClipboardText() try return the clipboard as text on error return missing value end try end getClipboardText on ensureDir(d) do shell script "mkdir -p " & quoted form of d end ensureDir on writeTextUTF8(p, t) try set fRef to open for access (POSIX file p) with write permission set eof of fRef to 0 write t to fRef as «class utf8» close access fRef on error try close access (POSIX file p) end try -- 頑丈なフォールバックとしてのpythonワンライナー書き出し set pyWrite to "import sys; import codecs; f = codecs.open(sys.argv[1], 'w', 'utf-8'); f.write(sys.argv[2]); f.close()" do shell script "python3 -c " & quoted form of pyWrite & " " & quoted form of p & " " & quoted form of t end try end writeTextUTF8 on splitByNewline(txt) set {oldTIDs, AppleScript's text item delimiters} to {AppleScript's text item delimiters, {return, linefeed}} set txtItems to text items of txt set AppleScript's text item delimiters to oldTIDs return txtItems end splitByNewline on sanitizeFilename(s) set illegal to "/:\\*?\"<>| " & linefeed & return set out to "" repeat with ch in characters of s if contents of ch is in illegal then set out to out & "_" else set out to out & ch end if end repeat return out end sanitizeFilename on clampLen(s, n) if (length of s) ≤ n then return s return text 1 thru n of s end clampLen on minInt(a, b) if a < b then return a return b end minInt ```