PHPを使用したLINE botからWordPress下書き自動作成までの道のり with 今週のひとこと

この記事は約23分で読めます。
スポンサーリンク

これまで大したアウトプットをしてこなかったゲーブラには珍しい技術のアウトプット記事です。
題の通りLINE botを起点にWordPressの下書きを自動で作成させたい!を実現するまでの流れをかなりザックリ残した記事です。

記事の記述内容はすっごい詳しい人が読んだら腰抜かすかもしれませんが、大目に見てください。

今週のひとこととは?

今週の更新記事のまとめと各メンバーが週の近況報告をする”ゆるいまとめ記事“。
PVはその週によりますが大体低いです。

以上。

自動化するに至った背景

  • メンバーの社会人率が上がったことによってそもそもの更新率が低下
  • 簡単な定期記事に時間を割くよりも他の物事に時間を割きたい
  • メンバーの単純なモチベーションダウン

わざわざ解説する必要もないであろう大体予想できる内容です。ひとこと記事の必要性や当サイトへの貢献度はともかく、単純作業を繰り返すのはそもそも面倒だし効率的ではないよねと私個人でも思ってました。

そして、社会人率が段々上がってきて今では100%。割ける時間もガクッと下がったわけですね。ブログが副業のサラリーマンとかホントすごいと思います。仕事中に内職してるでしょ?まあそれもライフハックかもしれないけど。

これまでの更新方法

これまでのひとことの更新手順をサラッとご紹介。

週の担当者がグループラインでひとことを募集

まずは毎週日曜日にその週の担当者がグループラインでひとことを募集します。

その告知後各自ひとことをグループライン宛に送信。開始も締め切りも人の手なので極端な話、募集告知がある前からひとこと書いちゃってもOKだし、締め切り後に「悪いけどコレも追加して!」と駆け込んでも担当者のココロの広さで許されたり。

締め切った後フォーマットに沿って記事を作成、投稿する

締め切り時間後に担当者は既に用意されているフォーマットに従って各自のひとことや今週投稿した記事をおしながきに貼り付けたりして投稿します。

既に全員分の吹き出しも用意されていて、手動とはいえできる限り効率化を図っていました。

これまでの流れは以上。週の担当者はあらかじめ決めてあり、何ヶ月かに一度担当者の忙しさとか見て担当メンバーの組み合わせを見直したりしていたりしてなかったり。

自動化に使用した機能とか

次に自動化に使用した機能を紹介します。とはいっても、WordPressでのサイト運営を既に行っていたので新たに導入したのはLINE botくらいです。

ロリポップサーバー

これがなければ始まらない。ゲーブラを運営するために借りているサーバー。代表してまささんが自らの財布を犠牲に借りてくれてます。(多分赤字)

導入時X Serverも検討した気がしますが、安さ手軽さでロリポップになりました。

サーバーって何?という方は下記サイト様をご覧ください。

IT用語を超簡単に解説してくれていてよくお世話になってます。後述するデータベースだったりPHPだったりもこのサイトでなんとな~く解説してくれてます。

データベース(MySQL)

同じくロリポップでサーバーをレンタルすると付属してくるサービス。全てのデータの保管場所。DBと略して表記したりします。

WordPress導入と同時にWordPressで使用するテーブルは自動で生成されるものの、自作テーブルの作成ももちろん可能。

今回は以下のテーブルを用意しました。

・定期記事担当者テーブル:
定期記事の週の担当者を保持しているテーブル。「今週のこの定期記事の担当者は3週目の日曜だから誰々さん担当ですよ」といった情報が取得できます。

・ひとことテーブル:
ひとことを保持するテーブル。誰がどういったひとことをいつ登録したかが確認できます。

PHP

サーバー側で動かしたい処理内容を記述したファイル。

サーバー側で動く処理のため読者側の端末では解析不可能。ひとことの自動化はこのPHPが肝となります。

今回はこちらから一方的にラインのメッセージを送りつける処理を記述したファイル送信されたメッセージに対して何かしらの処理を行い結果を返信するファイルの2つを用意しました。

前者をpush.php、後者をcallback.phpとします。

cron

任意のファイルを毎日、何曜日の何時など定期実行してくれるサーバーの機能。業務で使用経験があったのでひとこと自動化構想が挙がる前から知ってました。

ロリポップにも標準搭載。プランによって設定の柔軟さは異なりますが、1分ごとに実行したいといった要望がない限りはどのプランでも困らないと思います。今回はひとこと募集のメッセージをグループライン宛に毎週日曜に送ると同時に先週のひとことデータの削除の2つの動作を定期実行したかったので使用しました。

LINE Developers

コミュニケーションアプリLINEのアカウントを使用してメッセージを送信するといった基本動作など任意の処理が行えるラインが提供している機能。公式アカウントなど特定のメッセージに反応したりするアカウントはこの機能を使っています。

従来の募集形式を崩さないようにラインを使用したままひとことを集計できないか検討した結果メンバーのユウトさんが導入してくれました。今回彼が新たに作成したゲーブラLINEアカウント宛にひとことを送ると自動でデータベースに保存してくれるようにしました。

自動化の流れ

まずは自動化にあたってどういった流れで記事を投稿するか手順を考えましょう。大まかな流れはこれまでの手順を崩さず以下の通り。

LINE botがグループラインで毎週日曜日の決まった時間に募集の告知と担当者を教えてくれる
各自がグループラインでこれまで通りひとことをメッセージ送信する
週の担当者が任意のタイミングで締め切って、LINE botに下書き作成を命令する
作成された下書きを最終チェックして投稿

この手順をサーバーの機能やプログラミングを使って実現していきます。

cronによってPHPファイルを実行する

①を実現するために、まずはラインボットから募集開始告知を送信させます。今回はサーバーの標準機能cronによって指定した日時にPHPファイルを実施してLINE botを起動させます。とりあえず、毎週日曜のAM10ならみんないい感じに起きてて気付くかな。

ロリポップの場合、cronの設定できる個数はプランに依存します。制限個数を超える場合はプログラム内で日付を判断させて分岐するなど工夫が必要になると思います。

LINE botから募集開始メッセージを送る&DBから先週分のひとことデータを削除する

cronによってPHPファイルを起動させる設定が済んだら次は”募集告知の作成“、そして、もう必要がなくなるので同時に”先週のひとことデータの削除“も行いましょう。

募集開始告知の作成

募集告知は「募集!担当者〇〇、〇〇」の形式にします。募集開始!だけなら決まりきったメッセージですが、その週の担当者をくっつける必要があるので、まずは担当者をデータベースより取得します。

今回は上述したように定期記事の担当者を保持するテーブルを作成しました。定期記事の種類(post_kind)、何週目の担当か(week)、その週の担当者ID(admin_ID)があれば十分でしょう。

ちなみに、担当者IDはWordPress作成時に自動で生成されるテーブル(デフォだとusers)のIDと紐付くようにしています。IDからの名前解決もテーブルを結合させて同時に行います。

先週のひとことデータの削除

募集告知が終わったら次は先週のひとことデータを削除しましょう。

PHPファイル内でデータベースにアクセスしひとことテーブルの中身を丸ごと削除!特に凝ったことはしてません。

これによって募集する事前準備が整いました。
実装すると毎週日曜のAM10時にこんな感じで送られてきます。

ここまでの実装内容

担当者を取得し募集開始メッセージの送信ができるようになったので、一区切り。①が達成できました。

晒せないものもあるので一部置き換えていますが、おおよそ以下のような実装内容になりました。

実装内に度々登場しますが、WordPressで使用しているテーブルへのアクセスは$wpdbを使いましょう。記述量も減るし、セキュリティ面でもSQLインジェクションが回避できるので自力で組むより安全です。

// wpdbオブジェクト
global $wpdb;

// トークン類
$accessToken = 'LINE Developersで取得したトークン';
$group_id = '送信先となるグループのID(ユーザIDに置き換えるなど応用可能)';

$this_day = date('j'); #システム日(0埋め無)
$week = $this_day % 7 == 0 ? $this_day / 7 : ($this_day / 7) + 1; #第何〇曜日
$message_text_1 = "ひとこと、新作ゲーム紹介募集!\n担当者:"; # メッセージテキスト1
// クエリ発行(担当者取得)
$sql = $wpdb->prepare("SELECT u.display_name FROM 定期記事テーブル p INNER JOIN $wpdb->users u ON p.admin_ID = u.ID WHERE p.week = %d", $week);
// クエリ実行
$rows = $wpdb->get_results($sql);
// 検索結果が取得できた場合
if($rows){
  $count = count($rows);
  for($i = 0; $i < $count; $i++){
    $message_text_1 .= $rows[$i]->display_name;
    if($i + 1 != $count){
      $message_text_1 .= "、";
    }
  }
}
// お知らせと同時にひとこと保持テーブル削除
$sql = $wpdb->prepare("DELETE FROM ひとことテーブル;");
// クエリ実行
$wpdb->get_results($sql);


//メッセージ送信
sending_messages($accessToken, $group_id, $message_text_1);

/**
 * メッセージ送信メソッド
 * @param アクセストークン $accessToken
 * @param 送信先グループID $send_id
 * @param メッセージ内容 $message_text
 */
function sending_messages($accessToken, $send_id, $message_text){
    //レスポンスフォーマット
    $message_format_text = [
        "type" => "text",
        "text" => $message_text
    ];

    //ポストデータ
    $post_data = [
        "to" => $send_id,
        "messages" => [$message_format_text]
    ];

    //curl実行
    $ch = curl_init("https://api.line.me/v2/bot/message/push");
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_data));
    curl_setopt($ch, CURLOPT_HTTPHEADER, array(
        'Content-Type: application/json; charser=UTF-8',
        'Authorization: Bearer ' . $accessToken
    ));
    $result = curl_exec($ch);
    curl_close($ch);
}
イッキ
イッキ

LINE botの起動タイミングは”設定した任意の日時になる”もしくは”ユーザからメッセージが送信される”の2つ。序盤のPHPの項目で示した2つのファイルですね。

ここまでで解説したのは前者、push.phpです。

各メンバーがグループラインで送信しDBにひとことを保存する

さて、いよいよ各メンバーからひとことを受け付けます。上述した流れの②各自がグループラインでこれまで通りひとことを投稿する になります。
ここで大切になるのは”送られてきたメッセージをどうやってひとことと判断するか“、”メッセージの送信主は誰か

ユーザから送信されてきたメッセージへの反応の仕方、返信については下記エントリが分かりやすかったです。

送られてきたメッセージをどうやってひとことと判断するか

送られてきたメッセージがすべてひとことであるとは限りません。グループ内でメッセージを送りあっていれば、botには関係のないメッセージも多々あるわけで“これがひとことだ!”とbotが判断できる仕組みを用意しなければなりません。

イッキ
イッキ

ひとこと
今週もゲーム三昧!不労所得があるっていいよね。

ひとことのメッセージ形式はbot導入前から上記のように「ひとこと+改行+ひとことの内容」の形式と決まっていたので、ひとこと+改行が含まれていた場合にひとことと判断することにしました。

// ひとことの登録
if(strpos( $message_text, "ひとこと\n" ) === 0){
  // メッセージに「ひとこと改行」が含まれる場合
  $message_length = mb_strlen($message_text);
  $comment = mb_substr($message_text, 5, $message_length);
  $author_id = get_author_id($user_id); #ユーザIDからライターIDに変換

  if(strcmp($comment, "") != 0){
    // ひとことテーブルに既にデータがないか確認
    $sql = $wpdb->prepare("SELECT * FROM ひとことテーブル WHERE comment_author = %d;",$author_id);
    $rows = $wpdb->get_results($sql);
    if($rows){
      // ひとことが既にある場合
      // ひとことテーブルアップデート
      $sql = $wpdb->prepare("UPDAT ひとことテーブル SET comments = %s, create_date = now()  WHERE comment_author = %d;",$comment,$author_id);
    } else {
      // ひとことがない場合
      // ひとことテーブルインサート
      $sql = $wpdb->prepare("INSERT INTO ひとことテーブル (comments, comment_author) VALUES (%s, %d);",$comment,$author_id);
    }
    // SQL実行
    $wpdb->get_results($sql);
    if(!$wpdb->print_error()){
      $return_message_text = "ひとことを登録しました";
    } else {
      $return_message_text = "error comments insert/update";
    }
  }
}

今になってみれば他に方法はあったのですが、現状の実装上、文頭だろうが文中だろうがひとこと+改行が入っていた場合はひとことと判断されます。

そして、WordPressで使用しているテーブルへのアクセスは$wpdbを使いましょう。

メッセージの送信主は誰か

次にメッセージの送信主について。送信されたメッセージデータに送信者のユーザIDや送信先のグループIDが含まれているので、あらかじめ全員分のユーザIDを調べておき、そのユーザIDによって送信主を判断します。

ユーザIDはLINEアカウント IDとは異なりデタラメな文字列になっていてアプリ内では確認不可能です。

よく使用する返信やメッセージ送信主についてのデータの取得方法は以下です。
メッセージタイプには文章以外にも写真などのファイルも判別可能みたいです。

$replyToken = $json_object->{"events"}[0]->{"replyToken"};        #返信用トークン
$message_type = $json_object->{"events"}[0]->{"message"}->{"type"};    #メッセージタイプ
$message_text = $json_object->{"events"}[0]->{"message"}->{"text"};    #メッセージ内容
$user_id = $json_object->{"events"}[0]->{"source"}->{"userId"};   #ユーザID
$group_id = $json_object->{"events"}[0]->{"source"}->{"groupId"}; #グループID

さて、メッセージ送信者の判別、ひとことの登録が終わると登録した旨返すようにしたのでライン上では以下のように表示されます。

また、作成したひとことテーブルにひとことも登録されました。ここまでで ②各自がグループラインでこれまで通りひとことを投稿する が達成できました。

LINE botに下書きを作成する命令を送り下書き記事を作成

次は③週の担当者が任意のタイミングで締め切って、LINE botに下書き作成を命令する です。

全員のメッセージが揃うもしくは締め切りのタイミングとなった場合、担当者がbot宛に下書き作成命令を送ります。下書きの作成がちょっと複雑。おおよそ以下の流れとなってます。

  • 記事をpostsに登録する
  • 登録したデータからIDを取得しguid(記事のパスとなる値)を作成、更新する
  • 登録した記事に対してライターカテゴリ(記事内の著者とはまた別)を付与する
  • 登録した記事に対してカテゴリを付与する

本来はこれらを一気に行って、途中でダメだった場合はロールバックするべきなんでしょうけど、身内だけで使用する処理なのでそこまで凝る必要はないかなって。

WordPressの場合、投稿内容や投稿タイトル、投稿日時や投稿のバックアップ、その投稿が下書きか公開済みかなどのステータスはpostsに格納されます。
DBの構造は以下を参考にしてください。

ついでに実装も一部省略した上で載せておきます。さらに、例によって$wpdbを使いましょう。

// ひとこと記事作成
if(strcmp($message_text, "ひとこと下書き作成") == 0){
  $this_month = date('n'); #システム月(0埋め無)
  $this_day = date('j'); #システム日(0埋め無)

  $author_id = get_author_id($user_id); # ライターID

  $post_content = create_weekly_comments_html(); # 記事htmlを作成
  $post_title = "ゲーブラライターたちの一週間 " . $this_month . "/" . $this_day; # 記事タイトル

  // 記事インサート
  $sql = $wpdb->prepare("
      INSERT INTO $wpdb->posts (post_author, post_date, post_date_gmt, post_content, post_title, post_status, ping_status, post_modified, post_modified_gmt)
      VALUES (%d, now(), (now() - INTERVAL 9 HOUR), %s, %s, 'draft', 'closed', now(), (now() - INTERVAL 9 HOUR));",
      $author_id, $post_content, $post_title);
  // SQL実行
  $wpdb->get_results($sql);
  if($wpdb->print_error()){
    $return_message_text = "error insert";
    return;
  }

  // 記事ID取得(guid更新のため)
  $sql = $wpdb->prepare("SELECT * FROM $wpdb->posts WHERE post_title LIKE '%ゲーブラライターたちの%' AND post_title != 'ゲーブラライターたちの一週間 ○/△×' AND post_status = 'draft' ORDER BY post_date DESC LIMIT 1");
  // SQL実行
  $rows = $wpdb->get_results($sql);
  if($wpdb->print_error()){
    $return_message_text = "error get guid";
    return;
  }
  $guid = $rows[0]->ID;
  $sql = $wpdb->prepare("UPDATE $wpdb->posts SET guid = CONCAT('https://game-brothers.com/?p=', %d) WHERE post_title = %s;", $guid, $post_title);
  // SQL実行
  $wpdb->get_results($sql);
  if($wpdb->print_error()){
    $return_message_text = "error update";
    return;
  }

  $term_taxonomy_id = get_term_taxonomy_id($user_id);
  // カテゴリ更新(ライターカテゴリ)
  $sql = $wpdb->prepare("INSERT INTO $wpdb->term_relationships (object_id, term_taxonomy_id, term_order) VALUES (%d, %d, 1)", $guid, $term_taxonomy_id);
  // SQL実行
  $wpdb->get_results($sql);
  if($wpdb->print_error()){
    $return_message_text = "error insert term author";
    return;
  }

  // カテゴリ更新(記事カテゴリ)
  $sql = $wpdb->prepare("INSERT INTO $wpdb->term_relationships (object_id, term_taxonomy_id, term_order) VALUES (%d, 37, 1)", $guid);
  // SQL実行
  $wpdb->get_results($sql);
  if($wpdb->print_error()){
    $return_message_text = "error insert term post";
    return;
  }

  $return_message_text = "ひとこと下書き記事を作成しました";
}

担当者が下書き記事を最終チェック、手動で投稿する

最後④の手順は出来上がった記事に不具合が生じていないか確認して投稿ボタンを押すだけ。動なのでプログラミングは絡んできません。

手順にすると意外と単純。
ただ、PHPなんかやったことのないへっぽこSEの自分には実装も大変でした。

callback.phpの実装は以下の通り。記事作成など既に実装を載せているものもあるので詳細は省略しています。

// wpdbオブジェクト
require_once('wp-load.php');
global $wpdb;

$accessToken = 'LINE Developersから確認';

//ユーザーからのメッセージ取得
$json_string = file_get_contents('php://input');
$json_object = json_decode($json_string);

//取得データ
$replyToken = $json_object->{"events"}[0]->{"replyToken"};        #返信用トークン
$message_type = $json_object->{"events"}[0]->{"message"}->{"type"};    #メッセージタイプ
$message_text = $json_object->{"events"}[0]->{"message"}->{"text"};    #メッセージ内容
$user_id = $json_object->{"events"}[0]->{"source"}->{"userId"};   #ユーザID
$group_id = $json_object->{"events"}[0]->{"source"}->{"groupId"}; #グループID
$room_id = $json_object->{"events"}[0]->{"source"}->{"roomId"}; #ルームID

//メッセージタイプが「text」以外のときは何も返さず終了
if($message_type != "text") exit;

// ひとことの登録
if(strpos( $message_text, "ひとこと\n" ) === 0){
}

// ひとこと記事作成
if(strcmp($message_text, "ひとこと下書き作成") == 0){
}

// ユーザIDよりライターIDを判断
function get_author_id($user_id){
}

// ひとこと記事内容作成
function create_weekly_comments_html(){
}

// ライターIDからひとことコメント吹き出しHTMLを作成する
function create_author_weekly_comment($author_id, $comments){
}

// ユーザIDよりライターごとのカテゴリIDを判断
function get_term_taxonomy_id($user_id){
}

最後に

WordPressでゴリゴリにこういったことをしてるブログ、もっというとゲームブログは少ないんじゃないかな。
プログラミングの解説をするサイトとか管理人自身がIT技術に明るい人だと裏ではこういったことを行ってデータを集計していたりするかもしれませんが。

ちなみに、もう一つ定期記事として新作ゲーム紹介というものがあり、既に更新が止まっている状態でこちらも自動化を図り復活させたいと他のメンバーが目論んでます。

よろしくお願いします。

お気軽にコメントをどうぞ!