Google Picker アップローダを作る【GAS】

Google Apps Scriptでアプリケーションを作る際に、ユーザからファイルをアップロードしてもらって、そのファイルを元に処理を行う(例えば、経理システムの吐き出したCSVファイルを取り込んで、データを整形するなど)ことがままあります。

そんな時、ユーザにいちいち「Google Driveにアップロードして」「そのファイルのIDを取得して」なんて作業は生産的とは言えません。また、そのファイルもユーザのドライブに各々アップロードしたものとなると、一元管理も難しくなります。そこで利用するのが、Google Picker APIで、これをアプリに組み込むと、アップローダにすることが可能です。

図:こんな感じのアップローダが作れる

使用するファイル・参考資料等

事前準備

Picker APIはGoogle Apps Scriptの為に用意されているライブラリではありませんが、JavaScriptからは利用できます。使う為にはデベロッパーキー(APIキー)が必要となるので、事前準備が必要です。事前準備の手順は以下の通りです。

  1. Google Cloud Consoleを開く
  2. Google Cloud Platformが開かれるので、左サイドバーから「APIとサービスの有効化」をクリック
  3. Picker API」を検索しクリックします。
  4. 有効化をクリックします。
  5. 引き続き、「認証情報を作成」をクリックします。
  6. 認証情報ウィザードが開かれます。使用するAPIでは、Picker APIを選択します。
  7. 必要な認証情報」をクリックすると、APIキーが手に入ります。これで準備完了。コピーしておきましょう。
  8. スクリプトエディタに戻り、OKを押して終了します。

図:Picker APIは活用範囲が広いです。

図:API Keyを生成して取得する

ソースコード

コピーしておいたAPIキーはソースコード内に記述が必要です。また、アップロード先のフォルダのIDも事前に取得しておき、ソースコード内に記述が必要です。

HTML側のコード

<link href="https://ssl.gstatic.com/docs/script/css/add-ons.css" rel="stylesheet" />
<script type="text/javascript" src="https://apis.google.com/js/api.js"></script>

<script type="text/javascript">
  var DEVELOPER_KEY = 'ここにAPIキーを記述する';
  var DIALOG_DIMENSIONS = {width: 750, height: 450};
  var pickerApiLoaded = false;
  var origin = google.script.host.origin;
  var parent = "ここにアップロード先フォルダのIDを記述する";
 
  //Google Picker API呼び出し
  gapi.load('picker', {'callback': function() {
    pickerApiLoaded = true;
  }});
 
  //ドラッグエリアを表示する
  google.script.run.withSuccessHandler(createPicker).withFailureHandler(showError).getOAuthToken();
 
  //Picker Dialogを表示する
  function createPicker(token) {
    if (pickerApiLoaded && token) {
      var uploadView  = new google.picker.DocsUploadView().setParent(parent);;
            
      var picker = new google.picker.PickerBuilder()
            .addView(uploadView)
            .hideTitleBar()
            .setOAuthToken(token)
            .setOrigin(origin)
            .setLocale("ja")
            .enableFeature(google.picker.Feature.NAV_HIDDEN)
            .enableFeature(google.picker.Feature.MULTISELECT_ENABLED)
            .setDeveloperKey(DEVELOPER_KEY)
            .setCallback(pickerCallback)
            .setSize(DIALOG_DIMENSIONS.width - 10,
                     DIALOG_DIMENSIONS.height -10)
            .build();
      picker.setVisible(true);
    } else {
      showError('Pickerをロード出来ませんでした。');
    }
  }
 
  //Callbackデータを受け取る
  function pickerCallback(data) {
    if (data.action == google.picker.Action.PICKED) {
      var length = data.docs.length;
      var urlbase;
      
      for(var i = 0;i<length;i++){
        var fileId = data.docs[i].id;
        var url = data.docs[i].url;
        var title = data.docs[i].name;
        
        //アップロード完了メッセージ 
        urlbase = "https://drive.google.com/file/d/" + fileId + "/view?usp=sharing"
        document.getElementById('result').innerHTML +=
            '<b>アップロードしたファイル:</b><br />ファイル名: <a href="' + urlbase + '" target="_blank">' + title + '</a><br />ID: ' + fileId + '<br>';
      }
    } else if (data.action == google.picker.Action.CANCEL) {
      google.script.run.messager("アップロードはキャンセルされました。");
    }
  }
 
  //エラー表示用
  function showError(message) {
    document.getElementById('result').innerHTML = 'Error: ' + message;
  }
</script>

<div id = "result"></div>
  • 今回はアップローダであるため、ファイル選択用のViewではなく、google.picker.DocsUploadViewを指定し、アップロード専用のViewとしてロードします。
  • アップロードしたファイルをループで回して、ファイル名とIDを結果として表示させています。
  • キャンセルした場合には、メッセージを表示してダイアログを閉じるようにしています。

GAS側のコード

function onOpen(e) {
  var ui = SpreadsheetApp.getUi();
  ui.createMenu('▼あぷろだ')
      .addItem('アップローダ', 'fileman')
      .addToUi();
}

function fileman() {
  var html = HtmlService.createHtmlOutputFromFile('Picker.html')
      .setWidth(750).setHeight(480);
  SpreadsheetApp.getUi().showModalDialog(html, 'ファイルアップローダ');
}

//Access Tokenを取得する
function getOAuthToken() {
  DriveApp.getRootFolder(); 
  //DriveApp.addFile("test");
  return ScriptApp.getOAuthToken();
}

//エラーメッセージの表示
function messager(msg){
  var ui = SpreadsheetApp.getUi();
  ui.alert(msg);
}
  • 今回、GAS側はほとんど仕事はありません。ダイアログを表示する部分だけが重要な部分です。
  • ダミーで「//DriveApp.addFile("test");」を入れて、一度権限実行しないと、Driveへの書き込み権限がスコープに入ってくれないので、必ず書き込みをする場合は含めておきましょう(でないと、readonlyになってしまう)

オプション

このアップローダはいろいろなオプションがDeveloper Guideを見ると用意されています。其の中で今回アップローダで有用なオプションがあります。

アップロードするファイル形式を制限

今回のスクリプトでは、アップロードするファイル形式は特に制限していません。しかし、画像のみに制限したいなどといった要望が出てくると思います。その場合、uploadviewに対して以下のコードに変更すると制限が可能です。この場合、ファイル形式のMIMETYPEが必要になります。今回はjpeg画像に限定してみましょう。

jpeg画像のMIMETYPEはimage/jpgとなります。

//Picker Dialogを表示する
  function createPicker(token) {
    //画像に限定する
    var mimeman = "image/jpg";
    if (pickerApiLoaded && token) {
      var uploadView  = new google.picker.DocsUploadView().setParent(parent).setMimeTypes(mimeman);
     
      var picker = new google.picker.PickerBuilder()
            .addView(uploadView)
            .hideTitleBar()
            .setOAuthToken(token)
            .setOrigin(origin)
            .setLocale("ja")
            .enableFeature(google.picker.Feature.NAV_HIDDEN)
            .enableFeature(google.picker.Feature.MULTISELECT_ENABLED)
            .setDeveloperKey(DEVELOPER_KEY)
            .setCallback(pickerCallback)
            .setSize(DIALOG_DIMENSIONS.width - 10,
                     DIALOG_DIMENSIONS.height -10)
            .build();
      picker.setVisible(true);
    } else {
      showError('Pickerをロード出来ませんでした。');
    }
  }
  • uploadViewのPickerに対して、setMimeTypes(mimeman)として、MIMETYPE制限を加えています。

ファイルは複数アップロードさせないようにする

今回のアップローダは複数ファイルを一気にアップロードできるようになっています。しかし、プログラム作成上これでは都合が悪い場合もあります。其の場合には、以下のコードをコメントアウトもしくは削除することで、単一アップロードとなり、ファイルを選択時にすぐにアップロードが開始されるようにもなります。

enableFeature(google.picker.Feature.MULTISELECT_ENABLED); をコメントアウト

アップロードするフォルダを選択する

今回のアップローダはアップロード先フォルダは固定にしてあります。しかし、一方でアップロード先フォルダが選択できたほうが良いケースもあるでしょう。其の場合には以下のようなコードに変更し、setParentオプションを外すとアップロード時にフォルダを選択することが可能になります。

//Picker Dialogを表示する
  function createPicker(token) {
    //画像に限定する
    if (pickerApiLoaded && token) {
      var uploadView  = new google.picker.DocsUploadView().setIncludeFolders(true);

      var picker = new google.picker.PickerBuilder()
            .addView(uploadView)
            .hideTitleBar()
            .setOAuthToken(token)
            .setOrigin(origin)
            .setLocale("ja")
            .enableFeature(google.picker.Feature.NAV_HIDDEN)
            .enableFeature(google.picker.Feature.MULTISELECT_ENABLED)
            .setDeveloperKey(DEVELOPER_KEY)
            .setCallback(pickerCallback)
            .setSize(DIALOG_DIMENSIONS.width - 10,
                     DIALOG_DIMENSIONS.height -10)
            .build();
      picker.setVisible(true);
    } else {
      showError('Pickerをロード出来ませんでした。');
    }
  }
  • setIncludeFoldersをTrueとしてuploadViewにオプションを追加するだけ。

図:左上にフォルダ選択ボタンが出現する。

上書きアップロードを実現する

Google Pickerのダイアログからのアップロードは常に「新規アップロード」の処理になります。また、あのダイアログの中身はiframeで外部のサイトを表示してるものになるため、CORSの関係で改造する事も出来ません。そのため、上書きアップロードのオプションを付けたいなと思ったら、自身で別に実装する必要があります。

そこで今回は、右下固定のチェックボックスをVuetifyで用意し、チェックを入れた場合のアップロードは、上書きになるようGAS側にリクエストを送り、ファイルのアップロード後に同じファイル名のもの対してDrive APIで上書きをするという処理を作る必要があります。

上書きオプションを用意する

VuetifyのCardCheckboxおよびCSSにて右下に固定化する形でウィンドウを用意。また今回はPickerをフルスクリーン表示して違和感ないように調整してあげます。

HTML側
<!-- 上書きオプション -->
<v-card width="140" class="upoption" outlined v-show="pflg == true">
  <v-card-text>
    <v-checkbox
      label="上書き"
      color="green"
      :input-value="checked1"
      hide-details
    ></v-checkbox>
  </v-card-text>
</v-card>
  • Vuetifyの変数にchecked1とpflgの2つを用意し、初期値をfalseにしておく
  • createPickerを呼び出した時点でchecked1をfalseで初期化、pflgをtrueにする
  • cancel時およびpickercallback時にpflgはfalseに戻すようにする
  • アップロード情報を受け取った時点でGAS側に送る値に、google.script.run.withSuccessHandlerにてchecked1の値も併せて送りつける
CSS側
/* Pickerをフルスクリーン化 */
.picker {
  height: 100% !important;
  width: 100% !important;
  top: 0 !important;
}

/* 上書きオプションをフローティング */
.upoption {
  z-index: 3000;
  position: fixed;
  right:40px;
  bottom:5px;
  background-color: transparent !important;
  border-color: transparent !important;
}

図:フルスクリーン化とオプション表示

上書きする処理を装備

GAS側では、HTML側から受け取ったアップ済みファイルの情報および上書きオプションの値をもって処理を分岐。上書きオプションの値がfalseの場合は、通常の処理を行って終了する。上書きオプションがtrueの場合、一旦新規でアップロードしたファイルでもって、「既存の同じファイル名のものにファイルをUpdateして、アップロード済みファイルを廃棄する」という処理をする必要があります。

//同一フォルダ内の同一ファイル名のファイルを探す
var targetfolder = "アップ場所のフォルダのID";
var myfileid = "アップ済みファイルのID";
var targetname = "更新対象のファイル名"
var targetid = "";

var files = DriveApp.searchFiles("'" + targetfolder + "' in parents and title contains '" + targetname + "'"); 

while (files.hasNext()) {
  var file = files.next();
  var tempfid = file.getId();

  //tempfidと一致する場合はスルーする
  if(tempfid == myfileid){
    //処理をスルー
  }else{
    //違う場合は処理対象として利用する
    targetid = tempfid;
    break;
  }
}

if(targetid == "" || targetid == undefined){
  //上書き処理は行わない
}else{
  //自身のファイルのBlobを取得する
  var blob = DriveApp.getFileById(myfileid).getBlob();

  //リソース指定
  let resource = {
    uploadType: "media",
  }

  //Drive APIでアプデする
  Drive.Files.update(resource, targetid, blob, {'supportsAllDrives':true});

  //myfileidのファイルは廃棄する
  Drive.Files.trash(myfileid,{'supportsAllDrives':true})
}
  • 新規ファイルアップロード後、対象のファイルのIDを取得しておく
  • ファイル名とアップ場所のフォルダIDを元にDriveApp.searchFilesで検索。同じファイルIDの場合はスルーし、異なるものがあったら、1個目のファイルを上書き対象とする
  • 新規ファイルのデータをBlobで取得し、Drive.Files.updateでアップデート更新を行う。
  • アップデートが完了したらアップロードしたファイルは不要なので、Drive.Files.trashで廃棄することで、結果的に上書きアップロードとなる。

実行結果

スプレッドシートのメニューより「あぷろだ」⇒「アップロード」で実行できます。以下の手順でファイルがアップロードされている事を確認しましょう。

  1. 実行するとダイアログが表示されます。
  2. ファイルを選ぶか?掴んでダイアログにドラッグアンドドロップする
  3. アップロードボタンを押すと指定したフォルダにファイルがアップロードされます。
  4. 指定したフォルダを開いてみて、無事にアップロードされているか確認。

図:アップロード中画面。

関連リンク

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)