はじめに
みなさん、WordPressでFooGalleryというものは使用したことありますか?
FooGalleryってのは、WordPressにあるプラグインの1つで、画像をいい感じに表示することができます。
(赤線で塗りつぶしているのは、モザイクのかわりです。悪質ないじめではないのでご安心ください。)
↑の表示方法以外に、タイル形式でスマホの写真一覧みたいなかんじで表示することもできます。というか、表示に関してはやりたいことたくさんあるので、使ってみたい方は別途調べてみてください!
私が所属している「KADAI INFO」では、FooGalleryを使用した記事を投稿しているのですが、毎日手動で更新するのが辛いとのこと。そこで、GASを使用してこの手動作業を一気に自動化させてみたいと思います。
ちなみに、「KADAI INFO」とはなんぞやって方や、FooGalleryがどんなものか調べるのがだるい人は「こちら」を見てくれると嬉しいかも
なぜ手動更新がだるいのか?
KADAI INFOでは、「毎日SNAP」という簡単にいうと毎日写真を追加していくコーナーがあります。ここでは、毎日1枚ずつ新しい写真をFooGalleryに追加し、先頭画像に持っていくという作業があります。(くそ分かりにくい画像を↓に貼ります)
↑の画像でやっている作業を再度まとめると
1.使用するFooGalleryを開く
2.FooGalleryにメディアから新しい画像を追加。(テスト2ってのを追加してます)
3.今追加した画像を最初に表示したいので、一番前の方までずらします(テスト2をテストの左にもっていってる)
以上の作業を終えた後、記事を更新し、KADAI INFOの公式Twitterで更新ツイートもします。(公式アカウントはこちら)
とまあ、やることは大したことないけど毎日するってのがなかなかしんどいらしい。キャンプや旅行中に更新・ツイートとか考えるだけでしんどそうだ。海外旅行中に更新した強者もいます・・・かわいそ
やってみよう
では、早速楽々更新を目指して作っていきます。にしては前置き長くね?
結論
先に自動更新方法の結論をのべると、FooGalleryのショートコードである「[foogallery id="26772"]」の中に引数として「attachment_ids」を与えます。ここで指定したメディアIDが全ての画像を完全にオーバーライドしてくれるので、無理やり表示画像を書き換えます。
これが
こう。編集画面では「SNAP上設置用」と「SNAP下設置用」があるので普通だったらこれらの画像が少なくとも1枚は表示されますが、強制的に「テストです」画像を表示しています。
準備するもの
●Application Passwords(WordPressのプラグイン。これを使用してGAS上で記事の更新としていく)
●Twitter Developerの登録(自動ツイートで使用します。参考サイトはこちら)
クライアントサイド
自動更新用記事の用意
まずは、自動更新するための基礎記事を最初に書いていきます。
ちなみにテキスト表記はこんんかんじ
まず、書いても書かなくてもいいゾーンは記事更新後も残る部分で、書いても抹消されるゾーンは書いたとしても更新時に削除されてしまう部分です。KADAI INFOでは書いても抹消されるゾーンに何か書くことはないため、ここに何か書きたい人は後程の作業で別途工夫する必要があります。
とりあえず、このまま記事を公開すると当然ですが以下のようになります。
画像にはいりきっていませんが、上の方にも書いても書かなくてもいいゾーンが書いてあります。あと、この時点ではGASでの更新は行ってないので書いても抹消されるゾーンが残っています。
投稿を確認したら、再度編集ボタンを押して、記事IDを取得します。このIDは後程使用します
↑の赤枠の部分です(今回は27240)
後程このIDは使用するのですが、間違えると他の記事を上書きしてしまうので、慎重にいきましょう
更新用の画像の準備
今回更新に使用する画像はGoogle Driveで管理するので、そこに先ほど取得したID名でフォルダを作成します。
readmeはまじで関係ないですが、自動ツイートのスプレッドシートは後程の自動ツイートに使用する文章をまとめるのに使用する予定です。先に作っておきましょう。
フォルダを作成したら、その中に更新に使用する画像をぶち込みます。今回はテストなので2枚しか入れませんが、何枚いれてもらっても大丈夫です!
自動ツイート用の文章の用意
次に記事更新後に自動ツイートするための文章を用意します。ツイートする内容が変化しない場合は1組作成すればいいですが、KADAI INFOでは下の画像でいうと、コメント部分を毎回別のものに書き換えてツイートしていたので、そこだけ複数用意しておきます。
また、自動更新する画像の順番は名前の昇順でいく予定ですので、今回の場合はIMG_001.PNGが最初で、次にIMG_002.PNGがくる感じです。
開発者サイド
作成するファイル
クライアントサイドとは別のフォルダに作成してください。ちなみに今回は下の2つを作成
gsファイルでは、自動更新や自動ツイートをしていきます。スプレッドシートでは、記事更新前の情報を一時保存するために使用します。
自動更新用コード
記事の更新にはGASを使用します。GASは2時間くらいしか触ったことないのでコードがぐちゃぐちゃでしかも、メソッドも中途半端に分けてますが許してください!あと、GAS用の拡張子だとコードに色ついてくれないみたいなので、代理で「js」にしてます。実際にコードの拡張子は「gs」にしてください。
// 記事の更新
function update(){
var siteUrl = 'http://kadai-info.com/'; // KADAI INFOのURL
var user = "WordPressのユーザーID"; // 投稿者のユーザーID(Appliocation PasswordsのIDではない)
var pass = "シリアル番号みたいなやつ"; // Application Passwordsで設定したパスワード
var mainFolderID = "IDフォルダとかツイート用のスプレッドシートが置いてあるフォルダのID"; // 毎日SNAP投稿用フォルダのID
var mainFolder = DriveApp.getFolderById(mainFolderID);
var imageFolders = mainFolder.getFolders();
var ssId = '自動ツイート用のスプレッドシートのID'; // AutoSnapUpdateスプレッドシートのID
var dbSheetName = 'db'; // AutoSnapUpdateスプレッドシートのシート名
var spUrl = "https://docs.google.com/spreadsheets/d/"+ssId+"/";
var spreadsheet = SpreadsheetApp.openByUrl(spUrl).getSheetByName(dbSheetName);
var cell = spreadsheet.getRange('A1');
var file_list = [];
if(imageFolders.hasNext()){ // 記事IDのフォルダが存在してたら
var imageFolder = imageFolders.next();
var imageFiles = imageFolder.getFiles();
var articleID = imageFolder.getName();
var apiUrl = siteUrl + 'wp-json/wp/v2/posts/'+articleID; // 記事投稿用のAPI
var attachment_ids = cell.getValue(); // 更新前の情報を取得する
while(imageFiles.hasNext()){ // 画像があれば
var imageFile = imageFiles.next();
file_list.push(imageFile);
}
if(file_list.length>=1){ // 画像があれば
file_list.sort(); // 画像を名前の昇順に並び替える
var imageFile = file_list[0];
var imageID = imageFile.getId();
var originalBase = 'http://drive.google.com/uc?export=view&id=';
var imageUrl = originalBase + imageID; // Google Drive上の画像のURLを作成する
var imageName = imageFile.getName().split(".")[0]+".jpeg";
var mediaID = uploadImage(siteUrl, user, pass, imageUrl, imageName);
mediaID = Number(mediaID["id"]); // WordPressのメディアに画像をアップロードし、その画像のIDを取得する
attachment_ids = mediaID + "," + attachment_ids; // 更新前の情報に新しい画像の情報を先頭に付け加える
cell.setValue(attachment_ids); // スプレッドシートに最新情報を記録する
var content = getContents(siteUrl,user,pass,articleID,attachment_ids); // 記事の本文を作成
var headers = {
'Content-Type': 'application/json',
'Authorization': 'Basic ' + Utilities.base64Encode(user + ":" + pass)
};
var payload = {
'content': content,
'featured_media': mediaID,
};
var options = {
'method': 'POST',
'muteHttpExceptions': true,
'headers': headers,
'payload': JSON.stringify(payload)
};
UrlFetchApp.fetch(apiUrl, options); // 記事の更新をする
tweet(); // ツイートする (別ファイルのメソッド)
imageFolder.removeFile(imageFile); // ファイルの削除
if(file_list.length-1 == 0){
mainFolder.removeFolder(imageFolder); // ファイルがなくなったらフォルダごと削除
cell.clear(); // スプレッドシートを初期化する
}
}
else{
mainFolder.removeFolder(imageFolder); // フォルダの削除
cell.clear(); // スプレッドシートを初期化する
}
}
}
// メディアに画像をアップロード
function uploadImage(siteUrl, user, pass, imageUrl, imageName) {
var apiUrl = siteUrl + 'wp-json/wp/v2/media';
var headers = {
'Content-Type': 'image/jpg',
'Content-Disposition': 'attachment;filename='+imageName,
'accept': 'application/json',
'Authorization': 'Basic ' + Utilities.base64Encode(user + ":" + pass)
};
var payload = false;
// エラーが出なくなるまで繰り返す
// Google Driveだと何故か失敗することがあるため、成功するまで粘る
while(payload == false){
payload = getPayload(imageUrl);
}
var options = {
'method': 'POST',
'muteHttpExceptions': true,
'headers': headers,
'payload': payload
};
var response = UrlFetchApp.fetch(apiUrl, options);
var responseJson = JSON.parse(response.getContentText());
return responseJson;
}
// payload取得
function getPayload(imageUrl){
try{
var payload = UrlFetchApp.fetch(imageUrl);
return payload;
}
catch(e){
return false;
}
}
// 本文の作成と取得
function getContents(siteUrl,user,pass,articleID,attachment_ids){
var apiUrl = siteUrl + 'wp-json/wp/v2/posts/'+articleID; // 記事投稿用のAPI
var response = UrlFetchApp.fetch(apiUrl);
var responseJson = JSON.parse(response.getContentText()); // json本文を変換して取得
var split_contents = responseJson["content"]["rendered"].split('<div class="foogallery'); // 0:本文 1:foo1 2:foo2+本文
var first_content = split_contents[0]; // 記事の上に書いてあった「書いても書かなくてもいいゾーン」が入ってる
var foogallery = '[foogallery id="26772" attachment_ids="'+attachment_ids+'"] [foogallery id="26936" attachment_ids="'+attachment_ids+'"]'; // 画像を強制上書きするためのFooGalleryのショートコードを作成。このとき「書いても抹消されるゾーン」が削除される(残したい人はsplitでの分け方を工夫する必要がある)
var last_content = split_contents[2].split('<div class="fg-loader"></div></div></div>');
last_content = last_content[last_content.length-1]; // 記事の下に書いてあった「書いても書かなくてもいいゾーン」が入ってる
var content = first_content + foogallery + last_content; // 本文を組み合わせる
return content;
}
// ツイート用スプレッドシートの取得
function getSpreadSheet(){
var ssId = '自動ツイート用のスプレッドシートのID';
var dbSheetName = 'シート名';
var spUrl = "https://docs.google.com/spreadsheets/d/"+ssId+"/";
var spreadsheet = SpreadsheetApp.openByUrl(spUrl).getSheetByName(dbSheetName);
return spreadsheet;
}
// 今日の曜日を取得
function getToday(){
var week = ["日", "月", "火", "水", "木", "金", "土"];
var date = new Date()
var i = date.getDay();
var today = Utilities.formatDate(date, 'Asia/Tokyo', 'M月d日')+"("+week[i]+")"
return today;
}
// ツイート内容の取得
function getTweetContents(){
var spreadsheet = getSpreadSheet();
var today = getToday();
var title = "【"+today+" "+spreadsheet.getRange('A2').getValue()+"】\n\n";
var theme = "今月のお題は「"+spreadsheet.getRange('B2').getValue()+"」\n\n";
var comment_list = spreadsheet.getRange('C2:C32').getValues();
var comment = "";
for(var i=0; i<comment_list.length; i++){
if(comment_list[i] != "" && comment_list[i] != null){ // ツイート分があれば
comment = comment_list[i]+"\n";
spreadsheet.getRange('C'+(i+2)).setValue(""); // 使用後は空にする
break; // ループから抜ける
}
}
var url = spreadsheet.getRange('D2').getValue();
var contents = title+theme+comment+url;
return contents;
}
// Twitterする
function tweet(){
var contents = getTweetContents();
makeRequest("statuses/update.json", {status: contents});
}
// TwitterにAPIリクエストを送る
function makeRequest(api_url, parameters) {
var twitterService = getTwitterService();
if (twitterService.hasAccess()) {
var url = 'https://api.twitter.com/1.1/' + api_url;
var response = twitterService.fetch(url, {
method: "post",
muteHttpExceptions: true,
payload: parameters
});
var result = JSON.parse(response.getContentText());
Logger.log(JSON.stringify(result, null, 2));
} else {
Logger.log(service.getLastError());
}
}
ところどころコメント書いていませんが、正直既に記事が長くなって筆者も飽きちゃいました。すみません。
簡単に流れだけ説明すると、
- 画像が保管されているフォルダの存在を確認
- 画像ファイルが存在しているか確認
- 画像ファイルを名前の昇順で並び替え
- 最初の1枚目をWordPressのメディアにアップロード
- 本文を再構成したもので記事を更新
- ツイートする
- 使用したファイルを削除
- ファイルが空になったらフォルダを削除
という感じです。なぜファイルやフォルダを最後に削除するのかというと、GoogleDriveの容量がもう死にかけだから!あと、今回は時間トリガーでこのコードをまわすので、一度投稿した画像を区別するためにも削除しています。
また、ツイートするときも使用したツイート文章は空にしていますが、これも区別するためです。空でないセルまでforで回して、文章が来たらそれを取得して、空にしてます。こうすることで1回目コードを実行したときは「テストです」を取得し、2回目は「テスト2です」を取得するようにします。
いざ実行!!
さあ、何がともあれ実行してみて具体的に結果を見ていきましょう!
実行するやつは、main.gsのupdateメソッドを実行します。
記事(1回目)
記事(2回目)
ツイート(1回目)
ツイート(2回目)
まとめ
無事FooGalleryをGASで自動更新することができました。今回は手動で実行しましたが、あとはトリガーを設定するだけなのでそこは省きます。
ツイート結果の部分で、1枚目と2枚目でコメント部分は変化ありますが、URLのアイキャッチが変化していません。これは、Twitterがわの仕様で、一度URLを投稿するとキャッシュを利用するためキャッシュが残ってる間は何回URLを投稿しても同じものになります。
KADAI INFOでのこの自動ツイートは1日に1回する予定ですので、キャッシュはそのときには既に消えてるので問題ありませんが、連続でつぶやく予定の人はtwitter側のURLキャッシュをする必要があります。まあ、そこは自身で頑張ってください!
ちなみに、WordPress側のアイキャッチはリアルタイムで更新されるので問題ありません!