概要
この投稿ではサードパーティーを用いない Google Apps Script と Javascript によるファイルピッカーを紹介します。これまでの問い合わせで、開発者側の仕様でサードバーティを使用しないファイルピッカーがあれば便利とのご意見が多かったため、このようなサンプルを作成しました。このファイルピッカーの特徴は、次の通りです。
- 特定のフォルダ下のファイルを取得できる。
- 自分のアカウントの Google Drive だけでなく、サービスアカウントの Drive も利用できる。
リポジトリはこちらです。
デモ
使用方法
この場合、サンプルスクリプトを実行するための使用方法です。実際に使用される際は、それに合わせて調整してください。
1. スプレッドシートを新規作成する
このサンプルスクリプトでは、スプレッドシートのサイドバーを利用しますので、新規でスプレッドシートを作成します。そして、スクリプトエディタを開きます。
2. Drive API を有効にする
Advanced Google services で Drive API を有効にしてください。 Ref
3. サンプルスクリプト
Google Apps Script side: Code.gs
下記のスクリプトを Google Apps Script ファイルへコピーペーストしてください。このサンプルでは、自分のGoogle Driveのファイルが表示されます。
function getFiles(e, rootFolderId, mimeType) {
const accessToken = ScriptApp.getOAuthToken(); // In this case, the files and folders are retrieved from your own Google Drive.
mimeType = mimeType || "*";
const data = {};
const idn = e || "root";
const url1 = `https://www.googleapis.com/drive/v3/files/${e}?fields=id%2Cname%2Cparents`;
const folderObj = JSON.parse(
UrlFetchApp.fetch(url1, {
headers: { authorization: `Bearer ${accessToken}` },
}).getContentText()
);
if (e == "root") e = folderObj.id;
data[e] = {
keyname: folderObj.name,
keyparent: idn == rootFolderId ? null : folderObj.parents[0],
files: [],
};
let pageToken = "";
do {
const q =
mimeType == "*"
? `'${e}' in parents and trashed=false`
: `'${e}' in parents and (mimeType = 'application/vnd.google-apps.folder' or mimeType = '${mimeType}') and trashed=false`;
const url2 = `https://www.googleapis.com/drive/v3/files?fields=nextPageToken%2Cfiles%28id%2Cname%2CmimeType%29&q=${encodeURIComponent(
q
)}&orderBy=name&pageSize=1000&pageToken=${pageToken}`;
const res = UrlFetchApp.fetch(url2, {
headers: { authorization: `Bearer ${accessToken}` },
});
const fileList = JSON.parse(res.getContentText());
if (fileList.files.length > 0) {
data[e].files = data[e].files.concat(
fileList.files.map(({ id, name, mimeType }) => ({
name,
id,
mimeType: mimeType == MimeType.FOLDER ? "folder" : mimeType,
}))
);
}
pageToken = fileList.nextPageToken;
} while (pageToken);
return data;
}
// DriveApp.getFiles() // This is used for automatically detecting the scope of "https://www.googleapis.com/auth/drive.readonly". Also, you can use the scope of "https://www.googleapis.com/auth/drive".
// When the file is selected, this function is run from `work(value)` in Javascript side.
function doSomething(id) {
// do something
var res = id;
return res;
}
// Please run this function.
function main() {
SpreadsheetApp.getUi().showSidebar(
HtmlService.createTemplateFromFile("index")
.evaluate()
.setTitle("Sample File Picker")
);
}
HTML & Javascript side: index.html
下記のスクリプトを HTML ファイルへコピーペーストしてください。
<style>
#filePicker {
text-align: left;
width: 95%;
font-size: 1em;
margin: auto;
height: 3em;
}
</style>
<select id="filePicker"></select>
<div id="result"></div>
<script>
const rootFolderId = "root"; // Please set the top folder ID here.
const emimeType = "*"; // In this case, all files are shown.
// const emimeType = "application/pdf"; // When you use this line, you can show only files with this mimeType.
// --- When you want to run the script for the selected file, please modify this function.
const work = (...args) =>
google.script.run.withSuccessHandler(output).doSomething(...args);
function output(res) {
document.getElementById("result").innerHTML = res;
}
// ---
const alldata = {};
const getFileList = (...args) =>
google.script.run.withSuccessHandler(importData).getFiles(...args);
getFileList(rootFolderId, rootFolderId, emimeType);
document.getElementById("filePicker").addEventListener("change", function () {
const { value, options } = this;
const disp = options[options.selectedIndex].text;
if (disp.includes("Folder") || disp.includes("../")) {
while (this.lastChild) this.removeChild(this.lastChild);
if (alldata[value]) {
importData({ [value]: alldata[value] });
return;
}
getFileList(value, rootFolderId, emimeType);
return;
}
work(value);
});
function importData(e) {
const key = Object.keys(e)[0];
if (!alldata[key]) alldata[key] = e[key];
const select = document.getElementById("filePicker");
const obj = e[key]["keyparent"]
? [
{ text: `./${e[key]["keyname"]}`, value: key },
{ text: "../", value: e[key]["keyparent"] },
]
: [{ text: `./${e[key]["keyname"]}`, value: key }];
obj.forEach(({ text, value }) => {
const option = document.createElement("option");
option.textContent = text;
option.value = value;
select.appendChild(option);
});
e[key]["files"].forEach(({ mimeType, name, id }) => {
const option = document.createElement("option");
option.textContent = mimeType == "folder" ? "[Folder]" + name : name;
option.value = id;
select.appendChild(option);
});
}
</script>
-
トップフォルダを変更する場合は、
const rootFolderId = "root";
をconst rootFolderId = "### folder ID ###";
のように変更してください。 -
また、mimeType でフィルタをしたい場合は、
const emimeType = "*";
をconst emimeType = "### mimeType ###";
に変更してください。
サービスアカウントを使用する場合
サービスアカウントを使用する場合は、Google Apps Script を下記のように変更してください。
// This script is from https://gist.github.com/tanaikech/20ea127a8e23a7c609f8d764c8b7ed7c
function getAccessTokenFromServiceAccount(scopes) {
const private_key =
"-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----\n"; // private_key of JSON file retrieved by creating Service Account
const client_email = "###"; // client_email of JSON file retrieved by creating Service Account
const url = "https://www.googleapis.com/oauth2/v3/token";
const header = { alg: "RS256", typ: "JWT" };
const now = Math.floor(Date.now() / 1000);
const claim = {
iss: client_email,
scope: scopes.join(" "),
aud: url,
exp: (now + 3600).toString(),
iat: now.toString(),
};
const signature =
Utilities.base64Encode(JSON.stringify(header)) +
"." +
Utilities.base64Encode(JSON.stringify(claim));
const jwt =
signature +
"." +
Utilities.base64Encode(
Utilities.computeRsaSha256Signature(signature, private_key)
);
const params = {
method: "post",
payload: {
assertion: jwt,
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
},
};
const data = UrlFetchApp.fetch(url, params).getContentText();
const obj = JSON.parse(data);
return obj.access_token;
}
function getFiles(e, rootFolderId, mimeType) {
const scopes = ["https://www.googleapis.com/auth/drive.readonly"];
const accessToken = getAccessTokenFromServiceAccount(scopes); // In this case, the files and folders are retrieved from Google Drive of the service account.
mimeType = mimeType || "*";
const data = {};
const idn = e || "root";
const url1 = `https://www.googleapis.com/drive/v3/files/${e}?fields=id%2Cname%2Cparents`;
const folderObj = JSON.parse(
UrlFetchApp.fetch(url1, {
headers: { authorization: `Bearer ${accessToken}` },
}).getContentText()
);
if (e == "root") e = folderObj.id;
data[e] = {
keyname: folderObj.name,
keyparent: idn == rootFolderId ? null : folderObj.parents[0],
files: [],
};
let pageToken = "";
do {
const q =
mimeType == "*"
? `'${e}' in parents and trashed=false`
: `'${e}' in parents and (mimeType = 'application/vnd.google-apps.folder' or mimeType = '${mimeType}') and trashed=false`;
const url2 = `https://www.googleapis.com/drive/v3/files?fields=nextPageToken%2Cfiles%28id%2Cname%2CmimeType%29&q=${encodeURIComponent(
q
)}&orderBy=name&pageSize=1000&pageToken=${pageToken}`;
const res = UrlFetchApp.fetch(url2, {
headers: { authorization: `Bearer ${accessToken}` },
});
const fileList = JSON.parse(res.getContentText());
if (fileList.files.length > 0) {
data[e].files = data[e].files.concat(
fileList.files.map(({ id, name, mimeType }) => ({
name,
id,
mimeType: mimeType == MimeType.FOLDER ? "folder" : mimeType,
}))
);
}
pageToken = fileList.nextPageToken;
} while (pageToken);
return data;
}
// DriveApp.getFiles() // This is used for automatically detecting the scope of "https://www.googleapis.com/auth/drive.readonly". Also, you can use the scope of "https://www.googleapis.com/auth/drive".
// When the file is selected, this function is run from `work(value)` in Javascript side.
function doSomething(id) {
// do something
var res = id;
return res;
}
// Please run this function.
function main() {
SpreadsheetApp.getUi().showSidebar(
HtmlService.createTemplateFromFile("index")
.evaluate()
.setTitle("Sample File Picker")
);
}
スコープについて
このサンプルスクリプトでは、// DriveApp.getFiles()
のコメント行によって、https://www.googleapis.com/auth/drive.readonly
のスコープを自動検出するように設定しています。この場合、UrlFetchApp
によって、https://www.googleapis.com/auth/script.external_request
のスコープも自動追加されます。
もしもさらに制限したスコープを使用したい場合は、Manifest ファイル(appsscript.json
)へhttps://www.googleapis.com/auth/drive.metadata.readonly
とhttps://www.googleapis.com/auth/script.external_request
のスコープを設定すると、このサンプルスクリプトは動作します。
もちろん、選択したファイルを他のスコープを使用するメソッドで使用する場合は、それぞれのスコープを追加してください。