このブログは「Movable Type Advent Calendar 2024」23日目の記事です。
「GoogleスプレッドシートからMovable TypeのData APIを使って記事を投稿できないか」
ある日、「GoogleスプレッドシートからMovable TypeのData APIを使って記事を投稿できないか」という相談を受けました。
Googleフォームから社員に記事を投稿してもらい、その内容を自動POSTしたい
さらに要望として「Googleフォームを使って社員が書いた記事を、そのままブログ投稿にしたい」という案も出てきました。フォームの回答はスプレッドシートに集約されるので、それを自動でMTに送れたら便利ですよね。社内情報やちょっとしたニュースをすぐ共有できる仕組みになりそうです。
直接記事化できれば、効率がかなり上がりそうだということで、まずは試験的に取り組んでみることにしました。
MTのData APIでSpreadSheetのAppScriptからPOSTしても、うまく受け取れない
しかし、実際にAppScriptでPOSTしてみるとエラーが出たり、うまく受信できなかったり。いろいろ調べるうちに、送信データとMT側の受け取り方に微妙な差があったようです。「同じJSONのつもりでも、なぜ通らない?」と首をかしげながら試行錯誤が続きました。
MT側で受け取れる構成にPOST値を調整するプラグイン「SpreadsheetFriendly」を開発してみた
デバッグしてみると、MT側でSpreadSheetのPOST値が、POSTDATAというキーに集約されていることがわかりました。
そこで「SpreadsheetFriendly」というプラグインを開発しました。これは、スプレッドシートから送られるデータを、MTで正しく扱いやすい形に調整してくれます。データ形式の変換を代行し、投稿までのハードルを下げるのが狙いです。
SpreadsheetFriendlyの使い方
プラグインをダウンロードして、MTにインストール
まずはプラグインを入手し、MTのプラグインフォルダに配置します。通常のプラグイン追加と同じ手順で、有効化すれば準備完了です。(ダウンロードは記事の最下部にあります)
フォームと回答を管理するSpreadSheetを用意
Googleフォームの回答先として使うスプレッドシートを用意し、そこにタイトルや本文など必要なカラムを作成しておきます。社員が簡単に入力できるよう調整しましょう。
SpreadSheetのAppScriptを記述
AppScriptのエディタを開き、POST先URLやパラメータの設定を行います。シートが更新されたタイミングでData APIにデータを飛ばすように設定しておくと便利です。検証用に作成したスクリプトをサンプルで記載します。
// 開発環境に基本認証をかけていたので、認証情報を記載
const username = '[[基本認証ユーザー名]]';
const password = '[[基本認証パスワード]]';
const basicAuth = Utilities.base64Encode(`${username}:${password}`);
function myFunction(e) {
// Movable Type DataAPIのエンドポイントとアクセストークン
const apiEndpoint = '[[DataAPIのエンドポイント]]';
const accessToken = '[[APIトークン]]';
const siteId = '[[投稿先のblog_id]]';
const catId = '[[投稿先のcategory_id]]';
const params = {
username: '[[ログインユーザー名]]',
password: '[[ログインパスワード]]',
clientId: '[[任意のアプリID]]'
}
let options = {
method: 'POST',
'muteHttpExceptions': true,
'validateHttpsCertificates': false,
'followRedirects': false,
headers: {
'Authorization': 'Basic ' + basicAuth,
'Content-Type': 'application/x-www-form-urlencoded'
},
payload: params
};
var sessionId;
try {
// Movable Type DataAPIにデータを送信
const response = UrlFetchApp.fetch(apiEndpoint + '/authentication', options);
const responseCode = response.getResponseCode();
const responseText = response.getContentText();
// ログ出力(エラーがあれば確認)
Logger.log(response);
Logger.log('Response Code: ' + responseCode);
Logger.log('Response Body: ' + responseText);
const json = JSON.parse(responseText);
if ( json.accessToken ) {
sessionId = json.accessToken;
}
} catch (error) {
Logger.log('Error: ' + error.message);
}
// フォーム送信イベントからデータを取得
//const sheet = e.source.getActiveSheet();
//const lastRow = sheet.getLastRow();
//const rowData = sheet.getRange(lastRow, 1, 1, sheet.getLastColumn()).getValues()[0];
// スプレッドシートの列に対応するデータを取得(カスタマイズが必要)
//const title = rowData[2]; // 例: タイトル列
//const body = rowData[3]; // 例: 本文列
//const category = rowData[4]; // 例: カテゴリ列
const title = '開発テストタイトル';
const body = '開発テスト本文';
const categories = [ { id: catId } ];
// Movable Typeに送信するデータ
const payload = {
site_id: siteId,
entry: {
title: title,
body: body,
categories: categories,
status: 'publish',
},
status: 2
};
// HTTPリクエストのオプション
options = {
method: 'post',
headers: {
'Authorization': `Basic ${basicAuth}`,
'X-MT-Authorization': 'MTAuth accessToken=' + sessionId,
//'Bearer': 'Bearer ${accessToken}',
'Content-Type': 'application/json'
},
payload: JSON.stringify(payload),
muteHttpExceptions: true
};
Logger.log(options);
try {
// Movable Type DataAPIにデータを送信
const response = UrlFetchApp.fetch(apiEndpoint + '/sites/' + siteId '/entries', options);
const responseCode = response.getResponseCode();
const responseText = response.getContentText();
// ログ出力(エラーがあれば確認)
Logger.log(response);
Logger.log('Response Code: ' + responseCode);
Logger.log('Response Body: ' + responseText);
// スプレッドシートに結果を記録(例: 最終列に投稿結果を記録)
const resultColumn = sheet.getLastColumn() + 1;
sheet.getRange(lastRow, resultColumn).setValue(responseCode === 200 ? 'Success' : 'Failed');
} catch (error) {
Logger.log('Error: ' + error.message);
sheet.getRange(lastRow, sheet.getLastColumn() + 1).setValue('Error: ' + error.message);
}
}
function uploadAsset(apiEndpoint, accessToken, filePath) {
// ファイルをGoogle Driveから取得
const file = DriveApp.getFileById(filePath);
const blob = file.getBlob();
const options = {
method: 'POST',
headers: {
'Authorization': `MTAuth accessToken=${accessToken}`,
'Authorization': `Basic ${basicAuth}`,
'Content-Type': 'application/json'
},
payload: {
file: blob,
path: '/',
site_id: 65
},
muteHttpExceptions: true
};
const response = UrlFetchApp.fetch(`${apiEndpoint}/assets/upload`, options);
const responseData = JSON.parse(response.getContentText());
if (response.getResponseCode() === 200) {
return responseData.id; // アップロードされたアセットのIDを返す
} else {
Logger.log('Asset upload failed: ' + response.getContentText());
return null;
}
}
各APIで使うには作り込みが必要だけど道は開けそう
現時点では、記事投稿の基本的な動作確認だけにとどまっています。他のAPI機能(タグやカテゴリー、ユーザー関連など)は未検証ですが、作り込めば色々と活用ができそうなので、今後はより幅広い機能を試してみたいと思います。
開発ご要望があればお気軽にご相談ください
画像やファイルをアセットとしてアップロードする部分は手をつけられていませんが、ここが実現すれば、フォームやシートから画像も含めて記事投稿が完結できるようになります。もしご興味がある方は、お気軽にご相談ください。