Google Apps ScriptでCloud Storageにファイルをアップする【GAS】
Google Apps Scriptは、パスワード付きPDFが作成できない事から、生成するためには外部のAPI等を利用しなければなりません。これ、Google Cloud Functionsでどうにかできないかなぁという考えている過程で、Hummus RecipeというNode.jsのモジュールが単体でPDF暗号化に対応しているので、これ使えないかなと実験中(Bufferから直接は暗号化できない為)。
そのHummus Recipeは通常はローカルのPDFファイルを受け取って、パスワード設定するのですが、クラウド上で実現するには、一旦Google Cloud Storageにアップしなければならいのではという事で、今回は、GASからGCSにアップする手段を作ります
リンク
目次
今回使用するスプレッドシートその他
※Cloud Storageサービスは有料のサービスです。その為、利用するには事前に課金設定(請求アカウントの設定)が必要になります。
事前準備
OAuth2.0認証ライブラリの追加
プロジェクトを移動
サービスアカウントの作成
図:Cloud Storage JSON APIを有効化する
図:APIの認証情報を作っておく
JSONキーファイルを取得して認証する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
//認証用各種変数 var tokenurl = "https://accounts.google.com/o/oauth2/token" var jsonkey = "ここにJSONファイルのIDを指定する"; //JSON Keyファイルを指定 //OAuth2認証を実行する function startoauth(){ //UIを取得する var ui = SpreadsheetApp.getUi(); //認証を実行する var service = checkOAuth(); ui.alert("認証が完了し、Access Tokenを取得しました。") } //Google DriveにあるサービスアカウントキーのJSONファイルを取得する function getServiceAccKey(){ //JSONファイルの中身を取得する var content = DriveApp.getFileById(jsonkey).getAs("application/json").getDataAsString(); return JSON.parse(content); } //OAuth2.0認証を実行する function checkOAuth() { //JSONファイルの中身を取得する var privateKeys = getServiceAccKey(); return OAuth2.createService('cloudstorage:' + Session.getActiveUser().getEmail()) //アクセストークンの取得用URLをセット .setTokenUrl(tokenurl) //プライベートキーとクライアントIDをセットする .setPrivateKey(privateKeys['private_key']) .setIssuer(privateKeys['client_email']) //Access Tokenをスクリプトプロパティにセットする .setPropertyStore(PropertiesService.getScriptProperties()) //スコープを設定する(スペースで分割して複数指定可能) .setScope('https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/devstorage.read_write'); } |
Google Picker用の準備
GAS側コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
function onOpen() { var ui = SpreadsheetApp.getUi(); ui.createMenu('▶作業実行') .addItem('認証実行', 'startoauth') .addItem('ファイル選択', 'fileman') .addToUi(); } //Pickerダイアログを表示するコード function fileman() { var html = HtmlService.createHtmlOutputFromFile('Picker.html') .setWidth(600).setHeight(425); SpreadsheetApp.getUi().showModalDialog(html, 'ファイルの選択'); } //Origin設定を取得する function getPickerInfo() { //originを取得する var origin = "https://script.google.com"; try{ SitesApp.getActivePage().getUrl(); origin = "https://sites.google.com"; }catch(e) { } //デベロッパーキーを取得する var devkey = "ここにPickerのAPIキーを入力してください。"; //Access Token, 親フォルダID, Developer Key, ダイアログのサイズ, originなどをセットし、return DriveApp.getRootFolder(); return { token: ScriptApp.getOAuthToken(), origin: origin, developerKey: devkey, dialogDimensions: {width: 600, height: 425} }; } |
- 必ず1回、startoauthにて認証の実行が必要です。
HTML側コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css"> <script type="text/javascript" src="https://apis.google.com/js/api.js"></script> <script type="text/javascript"> var pickerApiLoaded = false; var origin = google.script.host.origin; //Google Picker API呼び出し gapi.load('picker', {'callback': function() { pickerApiLoaded = true; }}); //OAuthにて認証作業 function getOAuthToken() { google.script.run.withSuccessHandler(createPicker) .withFailureHandler(showError).getPickerInfo(); } //Picker Dialogを表示する function createPicker(data) { if (pickerApiLoaded && data.token) { var docsView = new google.picker.DocsView() .setIncludeFolders(false) //.setMimeTypes('application/vnd.google-apps.spreadsheet') .setSelectFolderEnabled(false); //Pickerに値をセットする var picker = new google.picker.PickerBuilder() .addView(docsView) .enableFeature(google.picker.Feature.NAV_HIDDEN) .hideTitleBar() .setLocale('ja') .setOAuthToken(data.token) .setOrigin(origin) .setDeveloperKey(data.developerKey) .setCallback(pickerCallback) .setSize(data.dialogDimensions.width - 2, data.dialogDimensions.height - 2) .build(); //Pickerを表示する picker.setVisible(true); } else { document.getElementById("main").innerHTML = 'Pickerをロード出来ませんでした。'; } } //Callbackデータを受け取る function pickerCallback(data) { var action = data[google.picker.Response.ACTION]; if (action == google.picker.Action.PICKED) { var doc = data[google.picker.Response.DOCUMENTS][0]; var id = doc[google.picker.Document.ID]; var url = doc[google.picker.Document.URL]; var title = doc[google.picker.Document.NAME]; document.getElementById('result').innerHTML = '<b>You chose:</b><br>Name: <a href="' + url + '">' + title + '</a><br>ID: ' + id; google.script.run.uploadgcs(id); } else if (action == google.picker.Action.CANCEL) { document.getElementById('result').innerHTML = 'Picker canceled.'; } } //エラー表示用 function showError(message) { document.getElementById('main').innerHTML = 'Error: ' + message; } </script> <div id="main"> <button onclick='getOAuthToken()' class="action">ファイルを選択</button> <p id='result'></p> </div> |
Cloud Storageにバケットを用意する
ここまで準備ができたら、あとはCloud Storage側にバケットを用意します。以下の手順でバケットを用意して、アップロード用コードの為のバケット名を取得しておきます。
- Cloud Consoleにて、Cloud Storageを開き、バケットの作成をクリック
- 名前に半角英数字で命名する。これがバケット名になる。
- 作成をクリックして完了。
- 今回は更に、publicという名前のフォルダを用意しました。
図:名前は世界でユニークな名前をつけなければならない
ソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
//HTML側でチョイスしたファイルのIDを元に処理を開始する function uploadgcs(targetid,filename){ //バケット名の設定 var bucket = "drive2gcs"; //アップロード先フォルダを指定 var folders = "public/" + filename; //Access Tokenを取得 var service = checkOAuth(); if (!service.hasAccess()) { Logger.log("まずは認証を実行してください。 %s", service.getAuthorizationUrl()); return; } //指定ファイルを取得 var blob = DriveApp.getFileById(targetid).getBlob(); var bytes = blob.getBytes(); //アップロードURLを組み立て var url = "https://www.googleapis.com/upload/storage/v1/b/" + bucket + "/o?uploadType=media&name=" + encodeURIComponent(folders); //UrlfetchAppでアクセス var res = UrlFetchApp.fetch( url, { method:"POST", contentLength: bytes.length, contentType: blob.getContentType(), payload:bytes, headers: { Authorization: "Bearer " + service.getAccessToken() } } ); //レスポンスを取得 //var result = JSON.parse(res.getContentText()); var ui = SpreadsheetApp.getUi(); ui.alert("アップ完了"); } |
- バケットの名前は、drive2gcsにしてあります。
- Pickerからは、ファイルのIDとファイル名を引数で取得しています。
- publicフォルダ以下にファイル名を付けてアップロードするようにしています。
- URL組み立てでバケット名と格納先+ファイル名を繋げています。
- POST通信で取得したByteデータをGCS側へと渡しています。
- DELETEメソッドを使って、https://www.googleapis.com/storage/v1/b/バケット名/o/ファイル名にて、ファイルを削除する事も可能です。
ちなみに、レスポンスで取得した内容は以下のような感じ。getContentTextで取得できます。これで、Cloud Function側からファイルにアクセスできるようになったはず!!多分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
{ generation=xxxxxx, metageneration=1, kind=storage#object, selfLink=https://www.googleapis.com/storage/v1/b/drive2gcs/o/public%2F%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB_001.png, mediaLink=https://www.googleapis.com/download/storage/v1/b/drive2gcs/o/public%2F%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB_001.png?generation=xxxxx&alt=media, bucket=drive2gcs, storageClass=MULTI_REGIONAL, size=5392197, md5Hash=xxxxxxxxx, crc32c=/VYijA==, timeStorageClassUpdated=2019-05-26T19:32:32.735Z, name=public/ファイル_001.png, timeCreated=2019-05-26T19:32:32.735Z, etag=xxxxxxx, id=drive2gcs/public/ファイル_001.png/1558938752736055, contentType=image/png, updated=2019-05-27T06:32:32.735Z } |
図:無事にアップロードできました。
関連リンク
- Upload Files to Google Cloud Storage with Google Scripts
- Google Cloud Storage Signed URLs In Apps Script
- GcsStore overview - Google Cloud Storage and Apps Script
- gcsにgasからファイルを公開して、アクセス耐性100万倍させるゾッと
- GoogleCloudStorageでPythonからファイルをやりとりする方法
- Cloud Storage の料金
- NodejsでGoogle Cloud Storageへファイルをアップロードしてみる
こんにちは
本記事を参考に日々役立たせていただいていたのですが、
Error(‘Access not granted or expired.’);
というエラーが出るようになってしまいました。
何か解決方法をご存知ではないかと思いコメントさせていただきました。
お手数おかけしますが、ご確認いただけますでしょうか。
サービスアカウントなので本来は、自動でAccess Tokenが取得されるはずですが、たびたびGoogle Apps Scriptに於いて、「再度認証を求められた」というケースが主に6月のGoogleイベント後に発生したりしてるので、その影響下と思います。主に機能拡張に伴って、認証内容が無くなってしまい、再認証をすれば動くと思います。
以下のエントリーに自分が解答していますが、Google Apps Script APIのスイッチをオンにして再認証をしてみてください。
https://teratail.com/questions/278164