# [メタ情報]
# 識別子: マイライブラリ_dropboxAPI_Mac_GASデータ交換_exe
# システム名: マイライブラリ_dropboxAPI_Mac_GASデータ交換
# 技術種別: Misc
# 機能名: Misc
# 使用言語: Python
# 状態: 公開用
# [/メタ情報]
要約:
M2 Mac環境で、PythonによるDropbox APIおよびGoogle Apps Script (GAS)連携スクリプト群が紹介されている。`dropbox_auth_setup.py`はOAuth 2.0フローを用いてDropbox APIのアクセストークンとリフレッシュトークンを取得。ブラウザでの認証、ローカルサーバーでの認証コード受領、そしてトークン交換のプロセスを行う。`get_dropbox_link.py`は、このリフレッシュトークンでDropbox認証を維持し、指定されたローカルファイルパスからDropbox API経由で共有リンク(raw=1形式)を取得する。最後に、`send_request.py`は、取得したDropboxリンクとファイル名などの情報をGASエンドポイントへPOST送信し、MacとGoogleスプレッドシート間のデータ連携を自動化する。これらのスクリプトはM1/M2 Macの両方に対応している。
M2Mac
▫️DropboxのAPIからトークン取得
Python
<任意のフォルダパス>/dropbox_auth_setup.py
dropbox_auth_setup.py
```
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
dropbox_auth_setup.py (完全env適合化・M1/M2両対応決定版)
- 金庫(.env)から APP_KEY / APP_SECRET を安全にロードして認証を自動開始します。
- 物理パスのハードコードを完全排除した、最高水準のポータブル設計です。
"""
import os
import sys
import webbrowser
import http.server
import socketserver
import urllib.parse
import requests
from pathlib import Path
from dotenv import load_dotenv
# ===== 1. 環境設定(金庫 .env からの動的ロード & 環境差の自動解決) =====
dotenv_loaded = False
for dotenv_path in [
Path(__file__).resolve().parent / ".env",
Path(__file__).resolve().parent.parent / ".env",
Path.home() / "python_scripts" / ".env"
]:
if dotenv_path.exists():
load_dotenv(dotenv_path)
dotenv_loaded = True
break
# 環境変数から機密情報を取得
APP_KEY = os.getenv("APP_KEY")
APP_SECRET = os.getenv("APP_SECRET")
REDIRECT_URI = "http://localhost:8080"
AUTH_URL = "https://www.dropbox.com/oauth2/authorize"
TOKEN_URL = "https://api.dropboxapi.com/oauth2/token"
# 【自律停止ガード】必要な環境変数が不足している場合は即座に安全に処理を停止
if not APP_KEY or not APP_SECRET:
print("❌ [dropbox_auth_setup.py] 金庫(.env)に必要な APP_KEY / APP_SECRET が設定されていません。安全に自律停止します。", file=sys.stderr)
sys.exit(1)
# 📡 Step 1: 認証コードを取得する
class AuthorizationHandler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-type", "text/html; charset=utf-8")
self.end_headers()
query = urllib.parse.urlparse(self.path).query
params = urllib.parse.parse_qs(query)
if "code" in params:
code = params["code"][0]
self.wfile.write("
✅ 認証に成功しました!ターミナルに戻ってトークンを確認してください。
".encode("utf-8"))
print(f"\n📡 認証コードを受信しました: {code}")
# 🚀 Step 2: 認証コードをトークンと交換する
print("🔄 トークンへの交換をリクエスト中...")
res = requests.post(TOKEN_URL, data={
"code": code,
"grant_type": "authorization_code",
"client_id": APP_KEY,
"client_secret": APP_SECRET,
"redirect_uri": REDIRECT_URI
})
if res.status_code != 200:
print("❌ 認証に失敗しました:", res.text)
sys.exit(1)
tokens = res.json()
print("\n================================================================================")
print("🎉 認証情報を取得しました!以下の3行を .env へ登録・上書きしてください。")
print("================================================================================")
print(f"APP_KEY = ""{APP_KEY}\"")
print(f"APP_SECRET = ""{APP_SECRET}\"")
print(f"REFRESH_TOKEN = ""{tokens.get('refresh_token', '')}\"")
print("================================================================================\n")
# サーバーを安全に自律停止
sys.exit(0)
def main():
auth_link = f"{AUTH_URL}?client_id={APP_KEY}&redirect_uri={urllib.parse.quote(REDIRECT_URI)}&response_type=code&token_access_type=offline"
print("🌐 ブラウザを開いて認証を実行します。自動で開かない場合は、以下のURLを直接開いてください。")
print(f"\n👉 {auth_link}\n")
webbrowser.open(auth_link)
# ローカルサーバーを起動して認証コードのコールバック(戻り)を待つ
socketserver.TCPServer.allow_reuse_address = True
with socketserver.TCPServer(("", 8080), AuthorizationHandler) as httpd:
print("⏳ 待機中... (ブラウザでの認証完了をお待ちしております)")
try:
httpd.handle_request()
except KeyboardInterrupt:
print("\n🛑 キャンセルされました。")
if __name__ == "__main__":
main()
```
get_dropbox_link.py
<任意のフォルダパス>/get_dropbox_link.py
<任意のフォルダパス>/get_dropbox_link.py
◆M2 Mac用
```
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
get_dropbox_link.py (完全env適合化・M1/M2両対応決定版)
- .envから認証キー・リフレッシュトークンを動的にロード。
- 旧パスから新パス「mybox/mybox_1」へ自動追従し、Dropbox API経由で安全に共有リンク(raw=1)を取得。
- 万が一のDropbox通信エラー(アプリ停止やAPI遮断時)は、空文字を出力して「空欄フォールバック」を起動。
"""
import sys
import json
import re
import os
import dropbox
from pathlib import Path
from dotenv import load_dotenv
# ===== 1. 環境設定(金庫 .env からの動的ロード & 環境差の自動解決) =====
dotenv_loaded = False
for dotenv_path in [
Path(__file__).resolve().parent / ".env",
Path(__file__).resolve().parent.parent / ".env",
Path.home() / "python_scripts" / ".env"
]:
if dotenv_path.exists():
load_dotenv(dotenv_path)
dotenv_loaded = True
break
# 環境変数から機密情報を取得
APP_KEY = os.getenv("APP_KEY")
APP_SECRET = os.getenv("APP_SECRET")
REFRESH_TOKEN = os.getenv("REFRESH_TOKEN")
# 【自律停止ガード】必要な環境変数が不足している場合は即座に安全に処理を停止
if not APP_KEY or not APP_SECRET or not REFRESH_TOKEN:
print("❌ [get_dropbox_link.py] 金庫(.env)に必要なDropbox APIキーが設定されていません。安全に自律停止します。", file=sys.stderr)
sys.exit(1)
# 🔁 Dropboxオブジェクトを取得(リフレッシュ対応)
def get_dbx():
return dropbox.Dropbox(
oauth2_refresh_token=<個別のトークン文字列>,
app_key=APP_KEY,
app_secret=APP_SECRET
)
# 🔧 Dropbox内の相対パスを取得(M1・M2・外付けSSDの全パスへ自動追従)
def get_dropbox_path(local_path):
possible_roots = [
"<任意のフォルダパス>",
"<任意のフォルダパス>",
"/Volumes/NO3_SSD/mybox/mybox_1"
]
for root in possible_roots:
if local_path.startswith(root):
relative_path = local_path[len(root):]
return "/dropbox_1" + relative_path
# 💡 修正箇所:Dropboxフォルダ外でも強制終了せず、ログを残して空文字を返す
print(f"⚠️ 通知: ファイルがDropboxフォルダ内にありません\nlocal_path: {local_path}", file=sys.stderr)
return ""
# 🔗 共有リンクを取得または作成
def get_shared_link(dbx, dbx_path):
if not dbx_path:
return ""
try:
links = dbx.sharing_list_shared_links(path=dbx_path, direct_only=True).links
if links:
return convert_to_raw_url(links[0].url)
except Exception:
pass
# なければ新規作成
try:
link = dbx.sharing_create_shared_link_with_settings(dbx_path)
return convert_to_raw_url(link.url)
except Exception as e:
print("❌ Dropbox APIで共有リンクを作成できませんでした:", file=sys.stderr)
print(e, file=sys.stderr)
return ""
# 🔄 dl=0 → raw=1 変換
def convert_to_raw_url(url):
return re.sub(r'dl=0', 'raw=1', url)
# 🏁 実行エントリポイント
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: get_dropbox_link.py ", file=sys.stderr)
sys.exit(1)
local_path = sys.argv[1]
try:
dbx_path = get_dropbox_path(local_path)
if dbx_path == "":
# パスが空(Dropbox外)の場合はAPI通信をスキップして空行出力(フォールバック)
print("")
else:
print(f"📂 Dropbox API 送信パス = {dbx_path}", file=sys.stderr)
dbx = get_dbx()
link = get_shared_link(dbx, dbx_path)
print(link)
except Exception as e:
print(f"⚠️ Dropbox通信エラー(フォールバック実行): {e}", file=sys.stderr)
print("") # 空行を出力してAppleScript側のクラッシュを防止
```
◆M1 Mac用
```
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
get_dropbox_link.py (完全env適合化・M1/M2両対応決定版)
- .envから認証キー・リフレッシュトークンを動的にロード。
- 旧パスから新パス「mybox/mybox_1」へ自動追従し、Dropbox API経由で安全に共有リンク(raw=1)を取得。
- 万が一のDropbox通信エラー(アプリ停止やAPI遮断時)は、空文字を出力して「空欄フォールバック」を起動。
"""
import sys
import json
import re
import os
import dropbox
from pathlib import Path
from dotenv import load_dotenv
# ===== 1. 環境設定(金庫 .env からの動的ロード & 環境差の自動解決) =====
dotenv_loaded = False
for dotenv_path in [
Path(__file__).resolve().parent / ".env",
Path(__file__).resolve().parent.parent / ".env",
Path.home() / "python_scripts" / ".env"
]:
if dotenv_path.exists():
load_dotenv(dotenv_path)
dotenv_loaded = True
break
# 環境変数から機密情報を取得
APP_KEY = os.getenv("APP_KEY")
APP_SECRET = os.getenv("APP_SECRET")
REFRESH_TOKEN = os.getenv("REFRESH_TOKEN")
# 【自律停止ガード】必要な環境変数が不足している場合は即座に安全に処理を停止
if not APP_KEY or not APP_SECRET or not REFRESH_TOKEN:
print("❌ [get_dropbox_link.py] 金庫(.env)に必要なDropbox APIキーが設定されていません。安全に自律停止します。", file=sys.stderr)
sys.exit(1)
# 🔁 Dropboxオブジェクトを取得(リフレッシュ対応)
def get_dbx():
return dropbox.Dropbox(
oauth2_refresh_token=<個別のトークン文字列>,
app_key=APP_KEY,
app_secret=APP_SECRET
)
# 🔧 Dropbox内の相対パスを取得(M1・M2・外付けSSDの全パスへ自動追従)
def get_dropbox_path(local_path):
possible_roots = [
"<任意のフォルダパス>",
"<任意のフォルダパス>",
"/Volumes/NO3_SSD/mybox/mybox_1"
]
for root in possible_roots:
if local_path.startswith(root):
relative_path = local_path[len(root):]
return "/dropbox_1" + relative_path
# 💡 修正箇所:Dropboxフォルダ外でも強制終了せず、ログを残して空文字を返す
print(f"⚠️ 通知: ファイルがDropboxフォルダ内にありません\nlocal_path: {local_path}", file=sys.stderr)
return ""
# 🔗 共有リンクを取得または作成
def get_shared_link(dbx, dbx_path):
if not dbx_path:
return ""
try:
links = dbx.sharing_list_shared_links(path=dbx_path, direct_only=True).links
if links:
return convert_to_raw_url(links[0].url)
except Exception:
pass
# なければ新規作成
try:
link = dbx.sharing_create_shared_link_with_settings(dbx_path)
return convert_to_raw_url(link.url)
except Exception as e:
print("❌ Dropbox APIで共有リンクを作成できませんでした:", file=sys.stderr)
print(e, file=sys.stderr)
return ""
# 🔄 dl=0 → raw=1 変換
def convert_to_raw_url(url):
return re.sub(r'dl=0', 'raw=1', url)
# 🏁 実行エントリポイント
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: get_dropbox_link.py ", file=sys.stderr)
sys.exit(1)
local_path = sys.argv[1]
try:
dbx_path = get_dropbox_path(local_path)
if dbx_path == "":
# パスが空(Dropbox外)の場合はAPI通信をスキップして空行出力(フォールバック)
print("")
else:
print(f"📂 Dropbox API 送信パス = {dbx_path}", file=sys.stderr)
dbx = get_dbx()
link = get_shared_link(dbx, dbx_path)
print(link)
except Exception as e:
print(f"⚠️ Dropbox通信エラー(フォールバック実行): {e}", file=sys.stderr)
print("") # 空行を出力してAppleScript側のクラッシュを防止
```
▫️macとspreadsheetのデータ交換
Python
send_request.py
<任意のフォルダパス>/send_request.py
<任意のフォルダパス>/send_request.py
◆M1 Mac
```
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
send_request.py (完全env適合化・M1/M2両対応決定版)
- .envから GAS_URL を動的にロード。
- 機密ドメインパスを1文字も残さない完璧なクレンジング設計です。
"""
import sys
import os
import json
import requests
from pathlib import Path
from dotenv import load_dotenv
# ===== 1. 環境設定(金庫 .env からの動的ロード & 環境差の自動解決) =====
dotenv_loaded = False
for dotenv_path in [
Path(__file__).resolve().parent / ".env",
Path(__file__).resolve().parent.parent / ".env",
Path.home() / "python_scripts" / ".env"
]:
if dotenv_path.exists():
load_dotenv(dotenv_path)
dotenv_loaded = True
break
# 環境変数からGAS_URLをロード
GAS_URL = os.getenv("GAS_URL")
# 【自律停止ガード】必要な環境変数が不足している場合は即座に安全に処理を停止
if not GAS_URL:
print("❌ [send_request.py] 金庫(.env)に GAS_URL が設定されていません。安全に自律停止します。", file=sys.stderr)
sys.exit(1)
# 🔍 受け取った引数を確認
print("📥 受信引数:", sys.argv)
# コマンドライン引数を取得
if len(sys.argv) < 5:
print("❌ 引数が不足しています")
sys.exit(1)
dropbox_link, file_path, file_name, extn = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
# 🔍 受け取ったデータをログに記録
print("📥 受信データ from AppleScript:")
print(f" dropboxLink: {dropbox_link}")
print(f" filePath: {file_path}")
print(f" fileName: {file_name}")
print(f" extn: {extn}")
# 送信データ
data = {
"dropboxLink": dropbox_link,
"filePath": file_path,
"fileName": file_name,
"updateFlag": "ADD"
}
# 🔍 送信データを確認
print("📤 送信データ:", json.dumps(data, indent=2, ensure_ascii=False))
# GAS にデータを送信
try:
response = requests.post(GAS_URL, json=data)
response.raise_for_status()
gas_response = response.json()
print("📥 GAS レスポンス:", json.dumps(gas_response, indent=2, ensure_ascii=False))
if gas_response.get("status") == "success":
print("✅ GAS にデータが正常に送信されました!")
print("success")
else:
print(f"❌ GAS でエラー発生: {gas_response.get('message')}")
print("error")
except requests.exceptions.RequestException as e:
print(f"❌ GAS 送信エラー: {e}")
print("error")
```
◆M2 Mac
```
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
send_request.py (完全env適合化・M1/M2両対応決定版)
- .envから GAS_URL を動的にロード。
- 機密ドメインパスを1文字も残さない完璧なクレンジング設計です。
"""
import sys
import os
import json
import requests
from pathlib import Path
from dotenv import load_dotenv
# ===== 1. 環境設定(金庫 .env からの動的ロード & 環境差の自動解決) =====
dotenv_loaded = False
for dotenv_path in [
Path(__file__).resolve().parent / ".env",
Path(__file__).resolve().parent.parent / ".env",
Path.home() / "python_scripts" / ".env"
]:
if dotenv_path.exists():
load_dotenv(dotenv_path)
dotenv_loaded = True
break
# 環境変数からGAS_URLをロード
GAS_URL = os.getenv("GAS_URL")
# 【自律停止ガード】必要な環境変数が不足している場合は即座に安全に処理を停止
if not GAS_URL:
print("❌ [send_request.py] 金庫(.env)に GAS_URL が設定されていません。安全に自律停止します。", file=sys.stderr)
sys.exit(1)
# 🔍 受け取った引数を確認
print("📥 受信引数:", sys.argv)
# コマンドライン引数を取得
if len(sys.argv) < 5:
print("❌ 引数が不足しています")
sys.exit(1)
dropbox_link, file_path, file_name, extn = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
# 🔍 受け取ったデータをログに記録
print("📥 受信データ from AppleScript:")
print(f" dropboxLink: {dropbox_link}")
print(f" filePath: {file_path}")
print(f" fileName: {file_name}")
print(f" extn: {extn}")
# 送信データ
data = {
"dropboxLink": dropbox_link,
"filePath": file_path,
"fileName": file_name,
"updateFlag": "ADD"
}
# 🔍 送信データを確認
print("📤 送信データ:", json.dumps(data, indent=2, ensure_ascii=False))
# GAS にデータを送信
try:
response = requests.post(GAS_URL, json=data)
response.raise_for_status()
gas_response = response.json()
print("📥 GAS レスポンス:", json.dumps(gas_response, indent=2, ensure_ascii=False))
if gas_response.get("status") == "success":
print("✅ GAS にデータが正常に送信されました!")
print("success")
else:
print(f"❌ GAS でエラー発生: {gas_response.get('message')}")
print("error")
except requests.exceptions.RequestException as e:
print(f"❌ GAS 送信エラー: {e}")
print("error")
```