# [メタ情報] # 識別子: test1_exe # システム名: これはテストシステムです # 技術種別: Misc # 機能名: Misc # 使用言語: [] # 状態: 実行用 # [/メタ情報] on run {input, parameters} -- 入力がない場合のエラー処理 if input is {} then display alert "ファイルまたはフォルダが選択されていません。" message "Finderで処理したいファイルを選択してください。" return end if -- ファイル情報の取得 set filePath to POSIX path of item 1 of input if filePath contains "/Users/xxxxxxxx/" then set filePath to do shell script "echo " & quoted form of filePath & " | sed 's#^/Users/xxxxxxxx/#/Volumes/NO3_SSD/#'" end if set fileName to (do shell script "basename " & quoted form of filePath) set extn to do shell script "echo " & quoted form of fileName & " | sed -E 's/.*\\.(.*)$/\\1/'" -- 日本語などを除去してDropboxのファイル名形式に近づける set cutjp_fileName to do shell script " echo " & quoted form of fileName & " | sed -E ' s/[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9ア-ン゙゚]+/-/g; s/-([.][^.]+)$/\\1/; s/^-+(.*)$/\\1/; s/--+/-/g' " -- Python(仮想環境)でDropboxリンクを取得 set pythonPath to "/Users/xxxxxxxxm1/venv_dropbox/bin/python" set getLinkScript to "/Users/xxxxxxxxm1/python_scripts/get_dropbox_link.py" set dropboxLink to do shell script pythonPath & " " & quoted form of getLinkScript & " " & quoted form of filePath -- 整合性チェック:Dropboxリンクにcutjp_fileNameが含まれているか if dropboxLink does not contain cutjp_fileName then display dialog "Dropboxリンクと選択したファイルの名前が一致しない可能性があります。 推定ファイル名: " & cutjp_fileName & return & "このまま続行しますか?" buttons {"キャンセル", "続行"} default button "キャンセル" if button returned of result is "キャンセル" then return end if -- Python(仮想環境)で送信スクリプトを実行 set sendScript to "/Users/xxxxxxxxm1/python_scripts/send_request.py" set cmd to pythonPath & " " & quoted form of sendScript & " " & quoted form of dropboxLink & " " & quoted form of filePath & " " & quoted form of fileName & " " & quoted form of extn set response to do shell script cmd -- 結果を表示 display dialog response end run get_dropbox_link.py /Users/xxxxxxxx/python_scripts/get_dropbox_link.py /Users/xxxxxxxxm1/python_scripts/get_dropbox_link.py ◆M2 Mac M1 Mac共用 # get_dropbox_link.py — リフレッシュトークン対応版 import sys import json import re import dropbox # ✅ あなたのDropboxアプリ情報をここに記入 APP_KEY = "" APP_SECRET = "" REFRESH_TOKEN = "" # 🔁 Dropboxオブジェクトを取得(リフレッシュ対応) def get_dbx(): return dropbox.Dropbox( oauth2_refresh_token=REFRESH_TOKEN, app_key=APP_KEY, app_secret=APP_SECRET ) # 🔧 Dropbox内の相対パスを取得 def get_dropbox_path(local_path): possible_roots = [ "/Users/xxxxxxxx/Dropbox/dropbox_1", "/Volumes/NO3_SSD/Dropbox/dropbox_1" ] for root in possible_roots: if local_path.startswith(root): relative_path = local_path[len(root):] return "/dropbox_1" + relative_path print(f"ERROR: ファイルがDropboxフォルダ内にありません\nlocal_path: {local_path}", file=sys.stderr) sys.exit(1) # 🔗 共有リンクを取得または作成 def get_shared_link(dbx, dbx_path): try: APP_KEY = dbx.sharing_list_shared_APP_KEY(path=dbx_path, direct_only=True).APP_KEY if APP_KEY: return convert_to_raw_url(APP_KEY[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) sys.exit(1) # 🔄 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] dbx_path = get_dropbox_path(local_path) print(f"📂 Dropbox API 送信パス = {dbx_path}", file=sys.stderr) dbx = get_dbx() link = get_shared_link(dbx, dbx_path) print(link) m1xxxxxxxx # Google Apps Script API エンドポイント GAS_URL = "" # 🔍 受け取った引数を確認 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") * ============================== * 📌 mediaLibrary.gs(安定版 / 20:50) * ============================== */ /** * ============================== * 📌 ユーティリティ関数 (共通処理) * ============================== */ function safeTrim(v){ return (v == null ? "" : String(v)).trim(); } function generateUniqueWpidex(extn, existingWpidexList) { Logger.log(`🔍 generateUniqueWpidex() 実行 - extn: ${extn}`); if (!existingWpidexList || !Array.isArray(existingWpidexList)) existingWpidexList = []; let existingSet = new Set(existingWpidexList.flat().filter(x => typeof x === "string" && x.trim() !== "")); if (!extn || extn.trim() === "") extn = "tmp"; const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; let wpidex, isUnique = false, attempts = 0; while (!isUnique) { attempts++; if (attempts > 10) return "DEFAULT_WPIDEX." + extn; wpidex = Array.from({ length: 8 }, () => chars.charAt(Math.floor(Math.random() * chars.length))).join("") + "." + extn; isUnique = !existingSet.has(wpidex); } return wpidex; } function generateRandomWpid() { return Array.from({ length: 8 }, () => "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".charAt(Math.floor(Math.random() * 62))).join(""); } function getCurrentTimestamp() { return Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "yyyy-MM-dd HH:mm:ss"); } /** (D) ファイルパスから `pmedia` または `mmedia` を抽出 */ function extractMediaCategory(filePath) { let match = String(filePath).match(/\/(mmedia|pmedia)\//); return match ? match[1] : "unknown"; } /** (E) URL エンコード(NFC 正規化) */ function encodeUrl(url) { return encodeURI(String(url).normalize("NFC")); } /** (I) `sheet2` から拡張子の `select_no` を取得 */ function getSelectNo(extn, sheet2) { if (!sheet2) { Logger.log("❌ 拡張子リスト (sheet2) が取得できません。"); return "1"; } let extList = sheet2.getDataRange().getValues(); let selectNo = "1"; for (let i = 1; i < extList.length; i++) { if (extList[i][0] === extn) return extList[i][1]; if (extList[i][0] === "others") selectNo = extList[i][1]; } return selectNo; } /** (D) `filePath` から `mmedia` または `pmedia` を抽出し、それに続くパスを取得 */ function extractMediaPath(filePath) { let match = String(filePath).match(/\/(mmedia|pmedia)\/(.+)/); return match ? match[1] + "/" + match[2] : ""; } /** (D) `wp_pathlink_url` を生成 */ function generateWpPathlinkUrl(filePath) { let mediaPath = extractMediaPath(filePath); return "https://xxxxxxxx.com/wp-content/" + mediaPath; } /** 1列検索(等価比較) */ function findRowInColumn(data, columnIndex, searchValue, mode = "DEFAULT") { for (let i = 1; i < data.length; i++) { if (mode === "EXCLUDE_ADD" && data[i][10] === "ADD") continue; if (safeTrim(data[i][columnIndex - 1]) === safeTrim(searchValue)) return i + 1; } return 0; } /** 値の存在判定 */ function findInColumn(data, columnIndex, valueToFind) { for (let i = 1; i < data.length; i++) { if (safeTrim(data[i][columnIndex - 1]) === safeTrim(valueToFind)) return true; } return false; } /** 異なる列組み合わせ検索 */ function findRowInColumnDifferent(data, columnIndex, value, matchValue) { for (let i = 1; i < data.length; i++) { if (safeTrim(data[i][columnIndex - 1]) !== safeTrim(value) && safeTrim(data[i][0]) === safeTrim(matchValue)) { return i + 1; } } return 0; } /** 除外行を指定して検索(そのまま比較版) */ function findRowInColumnExcluding(data, columnIndex, searchValue, excludeRows) { for (let i = 1; i < data.length; i++) { if (excludeRows.includes(i + 1)) continue; if (safeTrim(data[i][columnIndex - 1]) === safeTrim(searchValue)) return i + 1; } return 0; } F12_FlagWatcher.gs /** * F1_2(動画関係付けファイル)で I 列に TRUE が入ったら、 * 1) F2 の埋め込みコードを一括再生成(S列を更新し、U列=TRUE へ) * 2) U列の TRUE を検知して videoembed.json を Drive に保存 * 3) 処理後、F1_2 の I 列フラグを FALSE に戻す * * ※ このファイルは F1_2 のスプレッドシートに「バインド」してください。 * ※ トリガーは「インストール型トリガー: 編集時」を onF12FlagEdited_ に紐付けます。 */ /** ▼▼ 必要に応じて修正(F2 / F1_2 の ID, シート名) ▼▼ */ const F2_SPREADSHEET_ID = ""; // 動画パッケージ(F2) const F2_SHEET_NAME = 'シート1'; const F12_SHEET_NAME = '名前関係付け'; // F1_2 内のシート名 const VIDEOEMBED_DRIVE_FOLDER_ID = ""; // videoembed.json の出力先 const VIDEOEMBED_FILE_NAME = 'videoembed.json'; /** ▲▲ 必要に応じて修正 ▲▲ */ /** * F1_2(動画関係付けファイル)の I列フラグが TRUE になったら * 1) F2 の埋め込みコード再生成 * 2) videoembed.json 生成(Drive保存) * 3) F1_2 の I列の TRUE をまとめて FALSE に戻す * * - インストール型トリガー(編集時)でこの関数を紐づけてください。 */ function onF12FlagEdited(e) { // === 再入防止(自分の書き戻しで再トリガー → ループしないように) === var lock = LockService.getScriptLock(); if (!lock.tryLock(3000)) return; // 取れなければスキップ try { var sheet = (e && e.source) ? e.source.getActiveSheet() : null; if (!sheet) return; if (sheet.getName() !== '名前関係付け') return; // === I列(9列目)に TRUE が存在するかを確認(ヘッダ除く) === var lastRow = sheet.getLastRow(); if (lastRow < 2) return; var flagRange = sheet.getRange(2, 9, lastRow - 1, 1); // I2:I var flagValues = flagRange.getValues(); // TRUE が1つも無ければ何もしない var hasTrue = flagValues.some(function (r) { var v = r[0]; return v === true || String(v).toUpperCase() === 'TRUE'; }); if (!hasTrue) return; // === (1) F2 埋め込みコード再生成 === // EmbedLib を使う場合は EmbedLib.generateEmbedCode(); // 直呼びの場合は generateEmbedCode(); if (typeof EmbedLib !== 'undefined' && typeof EmbedLib.generateEmbedCode === 'function') { EmbedLib.generateEmbedCode(); } else if (typeof generateEmbedCode === 'function') { generateEmbedCode(); } // === (2) videoembed.json 生成(Driveへ保存) === if (typeof EmbedLib !== 'undefined' && typeof EmbedLib.generateEmbedJSON === 'function') { EmbedLib.generateEmbedJSON(); } else if (typeof generateEmbedJSON === 'function') { generateEmbedJSON(); } // === (3) I列の TRUE / 'TRUE' を一括で FALSE に戻す === for (var i = 0; i < flagValues.length; i++) { var v = flagValues[i][0]; if (v === true || String(v).toUpperCase() === 'TRUE') { flagValues[i][0] = false; } } flagRange.setValues(flagValues); // まとめて書き戻し(書き戻しで onEdit は再度走るが lock で抑止) } catch (err) { Logger.log('❌ onF12FlagEdited error: ' + err); } finally { lock.releaseLock(); } } "; $baseDir = __DIR__; // /wp-content/uploads/gd_wp_data $jsonFile = $baseDir . '/videoembed.json'; $bakDir = $baseDir . '/videoembed_bak'; $maxKeep = 20; // 認証 if (!isset($_GET['token']) || $_GET['token'] !== $EXPECTED_TOKEN) { http_response_code(403); echo json_encode(['status'=>'error','message'=>'invalid token']); exit; } // 入力JSON $raw = file_get_contents('php://input'); if ($raw === false || $raw === '') { http_response_code(400); echo json_encode(['status'=>'error','message'=>'empty payload']); exit; } $data = json_decode($raw, true); if (!is_array($data)) { http_response_code(400); echo json_encode(['status'=>'error','message'=>'invalid json']); exit; } // === サーバ側バリデーション === // videoid: 数字のみ/空禁止 // embedCode: 非空、かつ Vimeo プレースホルダを除外 $clean = []; foreach ($data as $it) { $videoid = isset($it['videoid']) ? trim((string)$it['videoid']) : ''; $embedCode = isset($it['embedCode']) ? trim((string)$it['embedCode']) : ''; // 1) videoid が空/非数字なら除外 if ($videoid === '' || !preg_match('/^\d+$/', $videoid)) continue; // 2) embedCode 必須 if ($embedCode === '' || $embedCode === '""' || strcasecmp($embedCode, 'null') === 0) continue; // 3) Vimeo プレースホルダ除外 $looksLikeVimeo = (bool)preg_match('/player\.vimeo\.com\/video|vm5\?movid=/i', $embedCode); $isPlaceholder = (bool)preg_match('/vm5\?movid=\s*$|player\.vimeo\.com\/video\/\s*$/i', $embedCode); if ($looksLikeVimeo && $isPlaceholder) continue; // 必須以外のキーはそのまま通す(title, playUrl, video2 など) $clean[] = $it; } // 0件なら上書きしない(配信を壊さない) if (count($clean) === 0) { echo json_encode(['status'=>'skip','reason'=>'no_valid_items','kept'=>is_file($jsonFile) ? filesize($jsonFile) : 0]); exit; } // 同一内容ならスキップ(無駄な書換え防止) $newJson = json_encode($clean, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); $oldJson = is_file($jsonFile) ? file_get_contents($jsonFile) : ''; if ($oldJson !== '' && hash('sha256', $oldJson) === hash('sha256', $newJson)) { echo json_encode(['status'=>'ok','action'=>'noop','count'=>count($clean),'bytes'=>strlen($newJson)]); exit; } // バックアップ準備 if (!is_dir($bakDir)) { @mkdir($bakDir, 0755, true); } // 既存をバックアップ if (is_file($jsonFile)) { $ts = date('Ymd_His'); @copy($jsonFile, $bakDir . "/videoembed.json.$ts.bak"); } // 原子的更新 $tmp = $jsonFile . '.tmp'; if (file_put_contents($tmp, $newJson, LOCK_EX) === false) { http_response_code(500); echo json_encode(['status'=>'error','message'=>'write tmp failed']); exit; } @rename($tmp, $jsonFile); // 古いバックアップ整理(新しい順で maxKeep 超過分を削除) $files = glob($bakDir . '/videoembed.json.*.bak') ?: []; rsort($files); for ($i = $maxKeep; $i < count($files); $i++) { @unlink($files[$i]); } // 完了 echo json_encode([ 'status' => 'ok', 'action' => 'updated', 'count' => count($clean), 'bytes' => strlen($newJson), 'path' => str_replace($_SERVER['DOCUMENT_ROOT'], '', $jsonFile), ]);