📖
Google Docs でブログ記事を管理する | Google Docs + SpreadSheet + GAS
概要
Google Docs でブログ記事を管理できるようにしたので、それについてのメモです。
目的
- Google Drive でファイルの管理(作成・更新・削除)ができる。
- ファイルは Google Docs で編集できるものにする。
- Google Drive のディレクトリ構造をそのまま URL に反映できる。
- コンテンツに変更を加えたとき、GAS の関数を起動するだけで更新できること。
- 最終的に、Astro.js などで SSR できること。
実装
1. Google Drive 上のディレクトリ構成
全体的なディレクトリ構成は以下のようになっています. なおサンプルのディレクトリも書いてあるので、*
で必須を示してあります
My Drive/
└ blog/ # *記事管理のルート
├ .dist/ # *GAS で処理されたファイルが出力されるところ
├ src/ # *記事データを入れるところ --- 以下はサンプル ---
├ / # Index ディレクトリ
└ hoge # Google Docs ファイル (URLは '/hoge')
└ foo/
└ foo_doc # Google Docs ファイル (URLは '/foo/foo_doc')
├ DataSheet # *データを管理するための SpreadSheet ファイル
└ GetGoogleDocs # *データを処理するための GAS ファイル
2. DataSheet にシートを作る
DataSheet という名前の Google SpreadSheet に exported_files という名前のシートを作成.
A1 から F1 に次の値を入れます.
セル番地 | --- | A1 | B1 | C1 | D1 | E1 | F1 |
---|---|---|---|---|---|---|---|
入力値 | --- | src_file_id | src_file_name | file_dir | file_path | dist_file_id | created_timestamp |
なお、後で使うので、シートIDを控えておきます.
3. GetGoogleDocs を編集する
ファイル構成
作成するファイルは以下の通りです.
- Logger.gs
- doGet.gs
- docExportAsHTMLFile.gs
- fetchDocAsHTML.gs
- FileNameDatabase.gs
- index.html
ライブラリ
二つのライブラリを使っています. ひとつはログ記録用の BetterLog、もうひとつは SpreadSheet を DB のように使える SpreadSheetSQL です.
-
BetterLog より
BetterLog
としてインポート -
SpreadSheetsSQL より
SpreadSheetsSQL
としてインポート
スクリプトプロパティ
次のようなスクリプトプロパティを設定します. シート ID や フォルダ ID を適切なものに置き換えてください.
-
DATASHEET_SS_ID
: DataSheet のシート ID -
DIST_FOLDER_ID
: MyDrive/blog/.dist ディレクトリのフォルダ ID -
SRC_FOLDER_ID
: MyDrive/blog/src ディレクトリのフォルダ ID
1. Logger.gs
BetterLog を使うための設定です.
Logger.gs
const _betterlog = BetterLog.useSpreadsheet(PropertiesService.getScriptProperties().getProperty("DATASHEET_SS_ID"));
const Logger = (() => {
const log = (label, msg = '') => {
const text = `[GetGoogleDocument] <${label}> ${msg}`;
_betterlog.log(text.trim());
}
return { log };
})();
2. doGet.gs
doGet.gs
// Get イベントが発生したときに起動する関数
function doGet(e) {
if (e === undefined) return;
// file という URL パラメータを持つ
const { file } = e.parameter;
// file が未指定のとき、index.html (リファレンス) を表示.
if (file === undefined) {
const template = HtmlService.createTemplateFromFile('index');
const htmlOutput = template.evaluate();
return htmlOutput;
// file が all のとき、すべてのコンテンツデータを JSON で渡す.
} else if (file === 'all') {
const data = FileNameDatabase.getAll();
return responseJson(data);
// file が src_file_id を指定しているとき、
// それに対応するコンテンツデータを渡す.
} else {
const { dist_file_id } = FileNameDatabase.getBySrcFileId(file);
if (dist_file_id === undefined) {
return responseJson({
error: 'No Such File ID'
});
}
// src_file_id から dist_file_id を取得して、ファイルインスタンスを生成
const fileApp = DriveApp.getFileById(dist_file_id);
// ファイルコンテンツを Blob で取得
const blob = fileApp.getBlob();
// Blob を Text に変換
const text = blob.getDataAsString();
return responseJson(text);
}
}
// Response 用関数: payload を JSON にして送る. 呼び出し側は Return しなければならない.
function responseJson(payload) {
const output = ContentService.createTextOutput();
output.setMimeType(ContentService.MimeType.JSON);
output.setContent(JSON.stringify(payload));
return output;
}
3. docExportAsHTMLFile.gs
ファイルに対する処理を行うメイン関数. ファイルを変更したら、これを起動する.
docExportAsHTMLFile.gs
function docExportAsHTMLFile() {
const SrcFolderId = PropertiesService.getScriptProperties()
.getProperty("SRC_FOLDER_ID");
const DistFolderId = PropertiesService.getScriptProperties()
.getProperty("DIST_FOLDER_ID");
const SrcFolderApp = DriveApp.getFolderById(SrcFolderId);
const DistFolderApp = DriveApp.getFolderById(DistFolderId);
/*
* recursiveComposer
* フォルダを深さ優先で探索し、
* 与えられた初期フォルダ内のすべてのファイルを取得する
*/
let currentPath = []; // 処理中の階層を保管する変数
const recursiveComposer = (outerFolder, handler) => {
/* 与えられたフォルダ内のすべてのフォルダ */
const folders = outerFolder.getFolders();
// 子フォルダの処理 (子フォルダがないときはスキップ)
while (folders.hasNext()) {
const folder = folders.next();
const folderName = folder.getName();
currentPath.push(folderName);
// 子フォルダをさらに探索
recursiveComposer(folder, handler);
}
/*
* 子フォルダがないとき、すなわち、
* 最下層や、それより下層のフォルダを探索しつくしたとき、
* 実行される. (子ファイルの処理)
*/
/* 与えられたフォルダ内のすべてのファイル */
const files = outerFolder.getFiles();
while (files.hasNext()) {
const file = files.next();
const fileName = file.getName();
const fileId = file.getId();
/* ディレクトリの配列版 */
const fileDirAry = currentPath;
const filePathAry = fileDirAry.concat(fileName);
/* ディレクトリの文字列版 */
let fileDir = '/';
if (fileDirAry[0] === '/' && fileDirAry.length > 1) {
// '/(Root)/L2/L3...' (Root 内のファイル) -> '/L2/L3...'
fileDir += fileDirAry.join('/').substring(1);
} else if (fileDirAry[0] !== '/') {
// '/L1/L2/L3...' -> '/L1/L2/L3...'
fileDir += fileDirAry.join('/');
}
const filePath = (fileDir === '/')
? `/${fileName}`
: `${fileDir}/${fileName}`;
// 各ファイルに対する処理である、handler を実行する
handler({
fileId, fileName, fileDir,
fileDirAry, filePath, filePathAry,
});
}
// 下層以外のディレクトリに移るため、
// 一番うしろ (現在のディレクトリ) はいらない
currentPath.pop();
}
recursiveComposer(SrcFolderApp, ({
fileId, fileName, fileDir,
fileDirAry, filePath, filePathAry,
}) => {
if (!FileNameDatabase.isExistSrcFileId(fileId)) return;
// ファイルに対する処理
// file を HTML として取得
const fileHTML = fetchDocAsHTML(fileId);
// その HTML をコンテンツにもつ .dist にファイルを作成
const distFile = DistFolderApp
.createFile(filePath, fileHTML, MimeType.HTML);
// Datasheet にメタデータを登録
FileNameDatabase.insertData({
fileId, fileName, fileDir,
filePath, distFileId: distFile.getId(),
});
}
});
}
4. fetchDocAsHTML.gs
Google Docs を HTML に変換して取得する関数.
fetchDocAsHTML.gs
function fetchDocAsHTML(fileId) {
const fileExportURL = `https://docs.google.com/feeds/download/documents/export/Export?id=${fileId}&exportFormat=html`;
// const storageUsed = DriveApp.getStorageUsed(); //これが無いと認証エラー
const contentAsHTML = UrlFetchApp.fetch(fileExportURL, {
method: 'get',
headers: {'Authorization': 'Bearer ' + ScriptApp.getOAuthToken()},
muteHttpExceptions: true,
}).getContentText();
return contentAsHTML;
}
5. FileNameDatabase.gs
DataSheet と読み書きするクラス.
FileNameDatabase.gs
const DatabaseSheetName = 'exported_files';
const DatabaseBookId = PropertiesService.getScriptProperties().getProperty("DATASHEET_SS_ID");
const FileNameDatabase = (() => {
const Database = SpreadSheetsSQL
.open(DatabaseBookId, DatabaseSheetName);
const getBySrcFileId = (fileId) => {
const res = Database.select([ 'src_file_id', 'src_file_name', 'file_dir', 'file_path', 'dist_file_id', 'created_timestamp' ])
.filter(`src_file_id = ${fileId}`).result();
// Logger.log('FileNameDatabase.getBySrcFileId()', JSON.stringify(res));
return res[0];
}
const getAll = () => {
const res = Database.select([ 'src_file_id', 'src_file_name', 'file_dir', 'file_path', 'dist_file_id', 'created_timestamp' ]).result();
return res;
}
const isExistSrcFileId = (fileId) => {
const res = Database.select([ 'src_file_id', 'src_file_name', 'file_dir', 'file_path', 'dist_file_id', 'created_timestamp' ])
.filter(`src_file_id = ${fileId}`).result();
return (res !== null && res.length > 0);
}
const insertData = ({ fileId, fileName, fileDir, filePath, distFileId }) => {
Database.insertRows([{
src_file_id: fileId,
src_file_name: fileName,
file_dir: fileDir,
file_path: filePath,
dist_file_id: distFileId,
created_timestamp: new Date(),
}]);
}
return {
getAll, isExistSrcFileId, insertData, getBySrcFileId,
}
})();
6. index.html
パラメータをなくして GET をしたときに表示するリファレンス.
index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<title>GetGoogleDocument</title>
<meta name="description" content="SendEmail は GAS からメールを送信するためのアプリケーションです。">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/default.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
</head>
<body>
<h1>概要</h1>
<p>
GetGoogleDocument は Google Drive 上の Google Document で HTML コンテンツを編集・管理するためのアプリケーションです。
また、リソースを外部から取得するための API を持っています。
</p>
<h1>仕様</h1>
<h2>API リクエスト</h2>
<table>
<tr>
<th>エンドポイント</th>
<td>https://script.google.com/macros/s/.../exec</td>
</tr>
<tr>
<th>HTTP メソッド</th>
<td>GET</td>
</tr>
<tr>
<th>GET プロパティ</th>
<td>file -- Source File ID を指定</td>
</tr>
</table>
<h2>GET プロパティ - file</h2>
Source File ID を指定します。'all' が指定されたとき、すべてのメタデータを返します。
<h2>レスポンス</h2>
<h3>通常</h3>
この、リファレンスを表示します。
<h3>GET プロパティ - all</h3>
下記のJSONを返します
<pre><code>
{
src_file_id: String,
src_file_name: String,
file_dir: String,
file_path: String,
dist_file_id: String,
created_timestamp: Number
}[]
</code></pre>
<h3>GET プロパティ - {Source File ID}</h3>
ドキュメントのHTMLテキストを返します
<script>hljs.highlightAll();</script>
</body>
</html>
使い方
- Google Drive の src にファイルを作成したら、GetGoogleDocs の
docExportAsHTMLFile
を実行する. そうすると、.dist にファイルが生成される. - GetGoogleDocs をウェブアプリとして公開.
例えば、https://script.google.com/macros/s/abcde-12345/exec
のようなURLが得られる.
-
https://script.google.com/macros/s/abcde-12345/exec
にアクセスすると、index.html が表示 -
https://script.google.com/macros/s/abcde-12345/exec?file=all
にアクセスすると、ファイルのメタ情報 (DataSheet の内容) が全表示 -
https://script.google.com/macros/s/abcde-12345/exec?file=hogehoge
にアクセスすると、hogehoge という src_file_id を持つファイルのコンテンツデータ (HTML) が表示
file
を指定した時の戻り値は JSON なので、 JavaScript から自由に Fetch できる.
Discussion