MENU

Google Apps Script(GAS)を使ってスプレッドシートに入力した記事をワードプレスに自動投稿する方法 [スクリプト編]

はじめに

WordPress(ワードプレス)でブログやサイト運営をしていると、エクセルとかスプレッドシートなどで記事を管理して自動投稿を出来ないかと思ってきました。ということでWordPressのREST APIを使ってGoogle スプレッドシートから記事を自動投稿するまでの流れを数回に分けて具体的にまとめようと思います。の続きです。

前回の記事はこちら

早速、コードを作成していきます。
まずはそれぞれ必要な処理を流れに沿って作っていきます。
細かい処理の説明はコード上でコメントで残してます。

この記事を読んでる人におすすめの書籍

スプレッドシートから記事を取得

記事を管理しているスプレッドシートのシート名を入力し、全記事(行)を取得・データにして返します。

// ------------------------------------------------
// Spreadsheetからデータを取得
// ------------------------------------------------
function getSpreadsheet(sheet_name){
  // スプレッドシートのオブジェクト取得
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  // スプレッドシートのシートオブジェクト取得
  const sheet = ss.getSheetByName(sheet_name);
  // 最終行のインデックス取得
  const lastRow = sheet.getLastRow();
  // 最終列のインデックス取得
  const lastCol = sheet.getLastColumn();
  // すべての行列の値を指定
  let values = sheet.getRange(1,1,lastRow, lastCol).getDisplayValues();
  // 最初の行(ヘッダー情報)を取得&配列から削除
  let headers = values.shift(); 
  let data = []
  // 最初の行(ヘッダー情報)をキーとしてオブジェクトの配列を作成
  values.forEach(function(value){
    let rowData = {};
    value.forEach(function(e, i){
      rowData[headers[i]] = e;
    });
    data.push(rowData);
  });
  return data.reverse();
}

返り値のデータ構造はヘッダーの各列名をキー、セルの値をバリューとしてオブジェクトの配列。

返り値データ

[ 
  { no: '1',
    post_id: '16485',
    post_status: 'delete',
    post_date: '2022-12-03',
    post_title: '記事タイトル',
    post_tags: 'タグ1,タグ2,タグ3,タグ4',
    post_category: 'カテゴリー',
    post_thumbnail: 'https://nanimonaikedo.jp/demo/media/eyecatch_sample.jpg',
    post_content: '本文テキスト本文テキスト本文テキスト本文テキスト' },
  { no: '2',
    post_id: '16489',
    post_status: 'publish',
    post_date: '2022-12-03',
    post_title: '【JavaScript】Vanilla JSで書かれたクッキー操作「js-cookie」',
    post_tags: 'javascript,html,css',
    post_category: 'coding',
    post_thumbnail: 'https://nanimonaikedo.jp/demo/media/eyecatch_cookie.jpg',
    post_content: '省略' },
  { no: '3',
    post_id: '',
    post_status: 'future',
    post_date: '',
    post_title: '【Javascript】Vue3のディレクティブまとめ',
    post_tags: 'Vue,javascript',
    post_category: 'coding',
    post_thumbnail: 'https://nanimonaikedo.jp/demo/media/eyecatch_vue.jpg',
    post_content: '省略' },
  { 
    ... 
  } 
]

取得した記事から「投稿・削除」する記事データだけを取得

取得したデータ(全記事)からキーの「post_status」をチェックして「future(投稿)」または「delete(削除)」になっているものだけを配列にして返します。
この時、deleteのデータはずべて取得、futureのデータは「num」で指定した数だけ取得します。

// ------------------------------------------------
// 記事投稿&削除するデータを取得
// ------------------------------------------------
function getPostData(data, num){
  let i = 0
  let dataPost = []
  // post_statusにfutureまたはdeleteになっている行取得
  data.some(function(value){
    if (value['post_status'] == 'delete'){
      dataPost.push(value);
    } else if (value['post_status'] == 'future'){
      // futureだけ数を指定
      if (i <= num) { 
        dataPost.push(value);
      }
      i++;
    }
  })  
  return dataPost
}

返り値のデータ構造は入力したデータ構造と同じで、post_statusが「future」または「delete」のオブジェクトの配列になっています。

返り値データ

[ 
  { no: '1',
    post_id: '16485',
    post_status: 'delete',
    post_date: '2022-12-03',
    post_title: '記事タイトル',
    post_tags: 'タグ1,タグ2,タグ3,タグ4',
    post_category: 'カテゴリー',
    post_thumbnail: 'https://nanimonaikedo.jp/demo/media/eyecatch_sample.jpg',
    post_content: '本文テキスト本文テキスト本文テキスト本文テキスト' },
  { no: '3',
    post_id: '',
    post_status: 'future',
    post_date: '',
    post_title: '【Javascript】Vue3のディレクティブまとめ',
    post_tags: 'Vue,javascript',
    post_category: 'coding',
    post_thumbnail: 'https://nanimonaikedo.jp/demo/media/eyecatch_vue.jpg',
    post_content: '省略' 
    } 
]

データをWordPressに投稿

ようやくここでWordPressに記事を投稿するコードを作成します。
上記で加工した記事データの他に、WordPressサイトの「URL」とWordPressの管理画面にログインに必要な「ユーザーID」、そして設定編で作成した「アプリケーションパスワード」が必要になります。

アプリケーションパスワードがわからない方はこちらの【設定編】を読んでください。

// ------------------------------------------------
// ワードプレスのREST APIを利用して記事投稿
// ------------------------------------------------
function postArticle(data, site_url_path, user_id, app_pass) {
  // 自動投稿用APIのURLをサイトURLから生成
  let apiURL = site_url_path + 'wp-json/wp/v2/posts/';
  // 投稿データごとに引数オジェクトを生成して投稿
  data.forEach(function(value){
    let postDate = new Date();
    let postId = value['post_id'];
    let postStatus = value['post_status'];
    // 登録タクソノミー, タクソノミー名からカテゴリーIDを取得
    let postCatId = getTaxonomyId(value['post_category'], 'categories', site_url_path, user_id, app_pass);
    // 登録タクソノミー, タクソノミー名からタグIDを取得
    let postTagId = getTaxonomyId(value['post_tags'], 'tags', site_url_path, user_id, app_pass);
    // 画像を登録して画像IDと画像パスを取得
    let [postImgId, postImgPath] = getImageData(value['post_thumbnail'], site_url_path, user_id, app_pass);
    // APIリクエストに必要なヘッダー情報を設定
    let headers = {
      'Content-Type': 'application/json',
      'Authorization': 'Basic ' + Utilities.base64Encode(user_id + ":" + app_pass)
    };
    if(postStatus == 'delete'){
      let apiPostURL = apiURL + postId.toString();
      // HTTP通信でDELETEするためオプション情報を設定する
      let options = {
        'method': 'DELETE',
        'muteHttpExceptions': true,
        'headers': headers,
      };
      // URLフェッチでAPIリクエストで記事削除
      let response = UrlFetchApp.fetch(apiPostURL, options);
      // 応答結果をJSON化して保存し、ログ出力(デバッグ用)
      console.log('Status code:', JSON.parse(response.getResponseCode()), postStatus);
      postStatus = 'trash'
    } else {
      // 記事投稿の詳細情報を記録したオブジェクトを設定する
      let arguments = {
        'date': postDate, // datetime
        'title': value['post_title'], // object型
        'content': value['post_content'], // object型
        'featured_media': postImgId, // 画像ID
        'status': 'publish',
        'categories':postCatId, // カテゴリIDの配列
        'tags':postTagId, // タグIDの配列
      };
      // HTTP通信でPOST(記事投稿)に必要なオプション情報を設定
      let options = {
        'method': 'POST',
        'muteHttpExceptions': true,
        'headers': headers,
        'payload': JSON.stringify(arguments)
      };
      // 記事投稿をリクエスト
      let response = UrlFetchApp.fetch(apiURL, options);
      // リクエスト結果をJSON化・ログ出力
      let statusCode = response.getResponseCode();
      console.log('Status code:', statusCode, value['post_title']);
      if (statusCode == 201){
        let postId = JSON.parse(response.getContentText())['id'];
        // 投稿記事と投稿画像の関連付け
        set_media_to_post(postImgId, postId, site_url_path, user_id, app_pass)
        value['post_id'] = postId;
      } else {
        console.log(JSON.parse(response.getContentText()));
      }
      postStatus = 'publish'
    }
    // spreadsheet更新箇所の値を格納
    value['post_status'] = postStatus;
    value['post_date'] = postDate;
  });
  return data
}

返り値のデータ構造はこれまでと同じで、post_idには投稿した時の記事ID、post_statusがpublishまたはtrashになり、post_dateには投稿した日時が入力されたオブジェクトの配列が返ってきます。

返り値データ

[ 
  { no: '1',
    post_id: '2231',
    post_status: 'trash',
    post_date: '2022/12/10',
    post_title: '記事タイトル',
    post_tags: 'タグ1,タグ2,タグ3,タグ4',
    post_category: 'カテゴリー',
    post_thumbnail: 'http://xxxxxxxxx.jp/xxxxxx/sample0.jpg',
    post_content: '本文テキスト本文テキスト本文テキスト本文テキスト' },
  { no: '3',
    post_id: '2232',
    post_status: 'publish',
    post_date: '2022/12/10',
    post_title: '【Javascript】Vue3のディレクティブまとめ',
    post_tags: 'Vue,javascript',
    post_category: 'coding',
    post_thumbnail: 'http://xxxxxxxxx.jp/xxxxxx/sample2.jpg',
    post_content: '省略' },
  { 
    ... 
  } 
]

上記のコードではカテゴリやタグ、サムネイル画像の登録処理も行われています(呼び出しています)。
それぞれの処理もコードだけ以下に記載して説明は割愛します。

GASをもっと学びたい人におすすめのコース

カテゴリーまたはタグを登録

カテゴリーまたはタグが登録済みかどうかチェックしてタクソノミーIDを返します。

// ------------------------------------------------
// カテゴリ、タグを登録&IDを返す
// ------------------------------------------------
function getTaxonomyId(strings, taxonomyType, site_url_path, user_id, app_pass) {
  let apiURL = site_url_path + 'wp-json/wp/v2/' + taxonomyType;
  // 文字列を「,」で分割
  let postTaxonomies  = strings.split(","); // string to array
  taxonomyIds = []
  // タクソノミーを登録リクエスト
  postTaxonomies.forEach(function(value){
    if (value == ''){
      return;
    }
    let arguments = {
      "name": value,
    };
    // APIリクエストに必要なヘッダー情報を設定
    let headers = {
      'Content-Type': 'application/json',
      'Authorization': 'Basic ' + Utilities.base64Encode(user_id + ":" + app_pass)
    };
    // HTTP通信でPOST(記事投稿)に必要なオプション情報を設定
    let options = {
      'method': 'POST',
      'muteHttpExceptions': true,
      'headers': headers,
      'payload': JSON.stringify(arguments)
    };
    // タクソノミ登録をリクエスト
    let response = UrlFetchApp.fetch(apiURL, options);
    // リクエスト結果をJSON化
    let responseJson = JSON.parse(response.getContentText());
    if (responseJson['code'] == 'term_exists'){
      taxonomyIds.push(responseJson['data']['term_id'])
    } else {
      taxonomyIds.push(responseJson['id'])
    }
  }); 
  return taxonomyIds
}

サムネイル画像をメディアに登録

画像をメディアに登録し、画像のパスと画像IDを返します。

// ------------------------------------------------
// 画像を登録&登録後のIDとパスを返す
// ------------------------------------------------
function getImageData(image_path, site_url_path, user_id, app_pass) {
  // 画像パスから画像データを取得リクエスト
  let imgResponse = UrlFetchApp.fetch(image_path, {'method': 'GET'});
  let apiURL = site_url_path + 'wp-json/wp/v2/media'
  // 画像のファイル名取得
  let filename = image_path.split("/").slice(-1)[0].split('.')[0];
  // APIリクエストに必要なヘッダー情報を設定
  let headers = {
    'Content-Type': 'image/png',
    'Content-Disposition': 'attachment;filename='+ filename +'.png',
    'accept': 'application/json',
    'Authorization': 'Basic ' + Utilities.base64Encode(user_id + ":" + app_pass)
  };
  // HTTP通信でPOST(記事投稿)に必要なオプション情報を設定
  let options = {
    'method': 'POST',
    'muteHttpExceptions': true,
    'headers': headers,
    'payload':imgResponse
  };
  // 画像登録をリクエスト
  let response = UrlFetchApp.fetch(apiURL, options);
  // リクエスト結果をJSON化
  let responseJson = JSON.parse(response.getContentText());
  imageId = Number(responseJson["id"])
  imagePath = responseJson["source_url"]
  return [imageId, imagePath]
}

登録した画像を投稿記事と関連付け

投稿した記事と投稿した画像を関連付けます。
この処理は行わなくてもサムネイル画像として表示されますが、WordPeressの管理画面 -> メディアで画像を見たときに、「アップロード先」で関連付けがされなくなります。
関連付けするだけなので返り値はなし。

// ------------------------------------------------
// 投稿記事と登録画像を関連付け
// ------------------------------------------------
function set_media_to_post(image_id, post_id, site_url_path, user_id, app_pass){
    let media_api_url = site_url_path + '/wp-json/wp/v2/media/' + image_id
    // APIリクエストに必要なヘッダー情報を設定
    let headers = {
      'Content-Type': 'application/json',
      'Authorization': 'Basic ' + Utilities.base64Encode(user_id + ":" + app_pass)
    };
    let arguments = { 
      'post': post_id
    }
    // HTTP通信でPUT(画像の関連付け)に必要なオプション情報を設定
    let options = {
      'method': 'PUT',
      'muteHttpExceptions': true,
      'headers': headers,
      'payload':JSON.stringify(arguments)
    };
    // 画像情報更新(関連付け)をリクエスト
    let response = UrlFetchApp.fetch(media_api_url, options);
    let statusCode = response.getResponseCode();
    console.log('Status code:', statusCode);
}

投稿した記事情報をスプレッドシートに更新する

ここまででスプレッドシートからWordPressに投稿しました。
最後に投稿した記事情報をスプレッドシートに更新します。
更新する箇所は「post_id」、「post_status」、「post_data」です。

// ------------------------------------------------
// Spreadsheetを更新
// ------------------------------------------------
function updateSpreadsheet(data, sheet_name){
  // スプレッドシートのオブジェクト取得
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  // スプレッドシートのシートオブジェクト取得
  const sheet = ss.getSheetByName(sheet_name);
  // 最終行のインデックス取得
  const lastRow = sheet.getLastRow();
  // 最終列のインデックス取得
  const lastCol = sheet.getLastColumn();
  // post_idの列インデックス取得
  let idCol = 0;
  for(var i=1; i<=lastCol; i++){
    if(sheet.getRange(1, i).getDisplayValue() === 'post_id'){
      idCol = i;
      break;
    }
  }
  // post_statusの列インデックス取得
  let statusCol = 0;
  for(var i=1; i<=lastCol; i++){
    if(sheet.getRange(1, i).getDisplayValue() === 'post_status'){
      statusCol = i;
      break;
    }
  }
  // post_dateの列インデックス取得
  let dataCol = 0;
  for(var i=1; i<=lastCol; i++){
    if(sheet.getRange(1, i).getDisplayValue() === 'post_date'){
      dataCol = i;
      break;
    }
  }
  data.forEach(function(value){
    for(var i=1; i<=lastRow; i++){
      // 投稿(id)の行を探す
      if(sheet.getRange(i, 1).getDisplayValue() === value['no']){
        // post_idの値を更新
        sheet.getRange(i, idCol).setValue(value['post_id']);
        // post_statusの値を更新
        sheet.getRange(i, statusCol).setValue(value['post_status']);
        // post_dateの値を更新
        sheet.getRange(i, dataCol).setValue(value['post_date']);
        return i;
      }
    }
  });
}

すべてのコードをまとめる

ここまで説明したコードを一気に実行出来るようにひとつにまとめます。
これで、「main()」を実行すればOK!

// ------------------------------------------------
// 各種変数
// ------------------------------------------------
const siteURL = 'WordPressサイトURL'
const userID = 'WordPressユーザーID';
const appPass = 'アプリケーションパスワード';
const sheetName = 'スプレッドシート名';
const postNum = 2

// ------------------------------------------------
// すべて実行
// ------------------------------------------------
function main() {
  let dataAll = getSpreadsheet(sheetName);
  let dataPost = getPostData(dataAll, postNum);
  if (dataPost.length) {
    let dataPosted = postArticle(dataPost, siteURL, userID, appPass);
    updateSpreadsheet(dataPosted, sheetName);
  }
}

まとめ

「main()」を実行すればOK!といってもどういうこと?だと思います。
このコードをGoogle Apps Scriptで実行する必要があるので、次回はGoogle Apps Script上での実行方法と、自動化のために定期実行する設定も含めて説明しようと思います。
おそらく次回で最後になります。あと1回頑張ってまとめます…では。

この記事を読んでちょっと難しそうと思った人にもおすすめ

次の記事はこちら

前の記事はこちら

nanimonai

プログラム書く方が楽しいデザイナーです。デザイン事務所→フリーランス→スタートアップ入社→フリーランス(現在)。 全然更新しないのでブログは向いてないと思いつつなんとかやってます。 よければTwitterのフォローもお願いします。

カテゴリー

キーワード

デザインに必須のフリー素材サイト



関連記事