Own products

  • 新規事業のアイディア創造機

Introduce

twitterのエゴサーチ結果をWordPressダッシュボードに表示するプラグイン「WP egosearch」公開しますっ!

この記事の読了時間:約3135

  • このエントリーをはてなブックマークに追加

春はあけぼの。
やうやう白くなりゆく山際、少し明かりて、紫だちたる雲間から降り注ぐ花粉!ぎゃーいやーやめてーー!!

ということで、この数日のところ案件の待ちタイムだったので、ちょっと前に思い立った「自分のブログのtwitterエゴサーチ結果をWordPress管理画面で見られるプラグイン」っつーのを作ってみましたよ。

ちょっと前だとtwitter検索結果をRSSで取得できたので、この手のヤツを作るのは比較的カンタンだったんですが、oAuth2導入から検索結果もAPIで取ってくれっつーカタチになったので、俄然めんどくさくなってました。

そして時は流れて2015年の春。

持て余した数日の余暇は、「Twitter Application-only authentication」と「wp_add_dashboard_widget()」と「curl_init()」によって、自分のサイトについて一週間以内につぶやかれたツイートWordPressダッシュボードに表示させるプラグインへと姿を変えたのです!

その名も「WP egosearch」!何のヒネリも無い、まんまやで!

んでは、ゴタクは置いといて機能紹介やら技術詳細やら、いってみよー!

twitterのエゴサーチ結果をWordPressダッシュボードに表示するプラグイン「WP egosearch」

インストール方法

実はコレ、すでに公式ディレクトリに登録されてるのです!

なので、上記ページからzipでダウンロードして自力でアップしても良し、管理画面のプラグイン検索で「WP egosearch」とか、単に「egosearch」とかでも見つかるので、そこからインストールしても良しです☆

公式ディレクトリに登録する&してからの苦悩と努力と挫折については、機会があったら後日にでも。。

これから公式登録を目指す諸君!大事なパスワードとか個人情報をコードの中に置き忘れたままコミットすると、基本取り返しつかないからな!!!

機能について

と、その前にカンタンに「エゴサーチ」とは、について。

自分の名前やハンドルネーム、運営してるブログの名前/URLなんかについて、自分で検索してその言及や反応を調べることを「エゴサーチ」といいます。

で、フツーはGoogleやTwitterなんかの検索窓に名前やURLなどを打って調べたり、専用のサービスやアプリケーションを使って調べたりするんですが、それをWordPressのダッシュボードでカンタンに見られるようにするのが、今回公開する「WP egosearch」プラグインです。

プラグインを導入したWordPressサイトのサイト名とURLを自動で取得して、それをTwitterの検索APIに突っ込んで、その結果をダッシュボードに一覧してくれるっつー、シンプルかつ便利なプラグインになります。

さらに、スパムっぽいリツイートなんかからの防御のために、「除外するキーワード」を設定できるというオマケ付き!

Twitterの検索APIを使うために、アプリケーションのコンシューマキー(API Key)とコンシューマシークレット(API Secret)が必要になるので、事前にTwitterで開発者アカウントを作成して、新たにアプリを作成するなり、検索APIを使ってない既存アプリをほじくり返したりして、キーとシークレットを取得して下さい。
取得の仕方は「Twitterのコンシューマーキー(Consumer key)等の取得 | offsidenowの日常を綴ったブログ」という記事や「TwitterAPIの認証コード類を取得する手順 – ysklog」という記事が非常に参考になりますよ!

あと1点、Twitterの検索APIは「過去1週間以内のツイート」しか探してくれませんので、ご注意を。。

それを突き止めるのに苦労しましたorz

「Wp egosearch」制作に関する技術について

「ダッシュボードにエゴサーチの結果を表示させる」というシンプルな作りのプラグインなので、実行ファイルは1つだけ。

必要になってくる処理としては、大きく分けて「Twitterの検索APIを使って、ブログに対する言及=エゴサーチ結果を取得する」ってのと「取得したエゴサーチ結果をダッシュボードに表示する」ってゆー2点。

せっかくなので、見難いコト必至だけど解説がてらババーっと下記に垂れ流してみましょー。

<?php
/*
Plugin Name: WP egosearch
Plugin URI: http://smkn.xsrv.jp/blog/2015/03/wordpress-plugin-called-wp-egosearch/
Description: Displays the egosearch(search your site URL/sitename) results of twitter in the dashboard.
Version: 1.1.0
Author: smkn
Author URI: http://smkn.xsrv.jp/blog/
License: GPL2 or later
*/

/* Copyright 2015 smkn (email : smkn.xxx@gmail.com)

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2, as
published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

class wpEgosearch {
	const WPES_PLUGIN_NAME = 'WP egosearch';
	const WPES_PLUGIN_VERSION = '1.1.0';

	public $site_url;
	public $site_name;
	public $consumer_key;
	public $consumer_secret;
	public $dissearched = array();
	public $read_count;

	function __construct(){
		$this->site_url = preg_replace('/^https?:\/\//iu', '', get_option('siteurl'));
		$this->site_name = get_option('blogname');
		$this->consumer_key = (get_option('wpes_consumer_key'))?get_option('wpes_consumer_key'):'';
		$this->consumer_secret = (get_option('wpes_consumer_secret'))?get_option('wpes_consumer_secret'):'';
		$this->dissearched = (get_option('wpes_dissearched'))?explode(',', get_option('wpes_dissearched')):array();
		$this->read_count = (get_option('wpes_count'))?get_option('wpes_count'):'10';
		load_plugin_textdomain('wp-egosearch', false, basename(dirname(__FILE__)).'/languages');
		add_action('wp_dashboard_setup', array(&$this, 'wpes_setup'));
	}
	public function wpes_setup(){
		wp_add_dashboard_widget('wpes_views', self::WPES_PLUGIN_NAME, array(&$this, 'wpes_views'), array(&$this, 'wpes_configure'));
	}

	public function wpes_views(){
		if(!empty($this->consumer_key) && !empty($this->consumer_secret)){
			/* get bearer token */
			$headers = array(
				'POST /oauth2/token HTTP/1.1',
				'Host: api.twitter.com',
				'User-Agent: '.self::WPES_PLUGIN_NAME.self::WPES_PLUGIN_VERSION,
				'Authorization: Basic '.base64_encode(urlencode(esc_attr($this->consumer_key)).':'.urlencode(esc_attr($this->consumer_secret))),
				'Content-Type: application/x-www-form-urlencoded;charset=UTF-8',
			);

			$ch = curl_init();
			curl_setopt($ch, CURLOPT_URL, 'https://api.twitter.com/oauth2/token/');
			curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
			curl_setopt($ch, CURLOPT_POSTFIELDS, 'grant_type=client_credentials');
			curl_setopt($ch, CURLOPT_POST, true);
			curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
			curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
			curl_setopt($ch, CURLOPT_HEADER, false);
			$response = curl_exec($ch);
			curl_close($ch);

			if($response === false){
				_e('curl - Token could not be retrieved.', 'wp-egosearch').'<br />';
				echo 'curl error:'.curl_error($ch);
			} else {
				$bearer = json_decode($response);
				if(isset($bearer->errors)){
					_e('Token could not be retrieved.', 'wp-egosearch').'<br />';
					echo 'bearer token error:'.$bearer->errors[0]->message;
				}
			}

			if($response !== false && !isset($bearer->errors)){
				/* get search results */
				$q_str = '"'.urlencode($this->site_url).'"+OR+"'.urlencode($this->site_name).'"';
				$dissearch_str = '';
				if(!empty($this->dissearched)){
					foreach($this->dissearched as $v){
						$dissearch_str .= '+-"'.urlencode(trim($v));
					}
				}
				$url = 'https://api.twitter.com/1.1/search/tweets.json?q='.$q_str.$dissearch_str.'&include_entities=true&result_type=recent&count='.esc_attr($this->read_count);
				$headers = array(
					'GET /1.1/search/tweets.json? HTTP/1.1',
					'Host: api.twitter.com',
					'User-Agent: '.self::WPES_PLUGIN_NAME.self::WPES_PLUGIN_VERSION,
					'Authorization: Bearer '.$bearer->access_token
				);

				$ch = curl_init();
				curl_setopt($ch, CURLOPT_URL, $url);
				curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
				curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
				curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
				$response = curl_exec($ch);
				curl_close($ch);

				if($response === false){
					_e('curl - Search results could not be obtained.', 'wp-egosearch').'<br />';
					echo 'curl response error:'.curl_error($ch);
				} else {
					$searched = json_decode($response);
					if(isset($searched->errors)){
						_e('Search results could not be obtained.', 'wp-egosearch').'<br />';
						echo 'search error:'.$searched->errors[0]->message;
					}
				}

				if($response !== false && !isset($searched->errors)){
					/* format results */
					$ego_data = array();
					foreach($searched->statuses as $k => $v){
						$ego_data[$k]['post_id'] = $v->id_str;
						$ego_data[$k]['date'] = date("Y.m/d H:i:s", strtotime($v->created_at));
						$ego_data[$k]['user'] = $v->user->screen_name;
						$ego_data[$k]['text'] = $v->text;
					}

					/* print results */
					echo '<div class="widget"><ul>';
					if(count($ego_data) > 0){
						foreach($ego_data as $v){
							echo '<li>';
							echo '<a href="https://twitter.com/'.$v['user'].'/status/'.$v['post_id'].'" target="_blank">@'.$v['user'].' said. - at '.$ego_data[$k]['date'].'</a>';
							echo '<p style="margin:0">'.$v['text'].'</p>';
							echo '</li>';
						}
					} else {
						_e('There was no corresponding tweet.', 'wp-egosearch');
					}
					echo '</ul></div>';
				}
			}
		} else {
			_e('Configure this widget by clicking the link in its upper right corner.', 'wp-egosearch');
		}
	}

	function wpes_configure() {
		if(isset( $_POST['wpes_consumer_key'])){
			update_option('wpes_consumer_key', sanitize_text_field($_POST['wpes_consumer_key']));
			update_option('wpes_consumer_secret', sanitize_text_field($_POST['wpes_consumer_secret']));
			update_option('wpes_dissearched', sanitize_text_field($_POST['wpes_dissearched']));
			update_option('wpes_count', sanitize_text_field($_POST['wpes_count']));
		}
		$feed_url = $this->consumer_key;

		echo '<label for="wpes_consumer_key"><span>twitter consumer key</span><input type="text" name="wpes_consumer_key" id="wpes_consumer_key" value="'.esc_textarea($this->consumer_key).'" size="45" /></label>';
		echo '<label for="wpes_consumer_secret"><span>twitter consumer secret</span><input type="text" name="wpes_consumer_secret" id="wpes_consumer_secret" value="'.esc_textarea($this->consumer_secret).'" size="45" /></label>';
		echo '<label for="wpes_dissearched"><span>'._e('Exclude keywords(Comma-separated)', 'wp-egosearch').'</span><input type="text" name="wpes_dissearched" id="wpes_dissearched" value="'.esc_textarea(implode(',', $this->dissearched)).'" size="45" placeholder="one thing,two,three" /></label>';
		echo '<label for="wpes_count"><span>'._e('Number to get(Up to 100. ※Only tweet for less than one week)', 'wp-egosearch').'</span><input type="text" name="wpes_count" id="wpes_count" value="'.esc_textarea($this->read_count).'" size="45" style="margin-bottom:10px;" /></label>';
	}
}
new wpEgosearch();

1~26行目 WordPressプラグインと認識させるための記述 + GPL表記

WordPressがこのファイルをプラグインとして認識するために必要な記述が3~9行目、GPLのアレが12~25行目になります。

これを書かないと認識されないし公式プラグインにも登録できないので必須。

書き方は公式のプラグインの作成 - WordPress Codex 日本語版のまんまです。

28行目 classの宣言

今回は関数の連打じゃなくclassで書いてみたので、その宣言です。

29~37行目 classで使い回す変数などの宣言

使いそうなヤツを目処付けて、上部に一括でまとめてます。

39~48行目 オブジェクト生成時に呼ばれる初期化処理

さっき宣言した使い回す変数に値を入れていきます。

サイト名は get_option('blogname') で、サイトURLは get_option('siteurl') で取れるのですが、URLは「http(s)://」を抜かないとtwitterで正確な検索結果が取れないっぽかったので、正規表現で当該部分を削除してます。

42~45行目では、すでに設定済みだったらDBから取ってきて、設定されてなかったりしたら空文字や空配列を値として入れるようにしときます。

46行目は、公式プラグインとして配布するのに必須の「多言語対応」の為の記述で、

load_plugin_textdomain(言語ファイルを呼び出すための一意の文字列, 決め打ちでfalse, 言語ファイルがあるディレクトリのパス);

って書き方になります。これはWordPress 3.7 以降での言語ファイルのロード方法 | Foreignkey, Inc.などで勉強させて頂きましたー!

んで47行目が、管理画面のダッシュボード生成時に動くフックと、そこで wpes_setup を発火させてね☆という指定になります。

49~51行目 ダッシュボードにウィジェットを追加させる

ダッシュボードにウィジェットを追加して wpes_views を動かしてちょ + 設定フォームとして wpes_configure を動かしてねん♪という記述。

これは公式のや、【WordPress】ダッシュボードに自作のウィジェットを追加する at softelメモを参考にしました。

53行目 wpes_views関数の宣言

Twitterを相手にアレコレする関数 wpes_views を宣言してます。

54行目 設定がされてるかどうかの判別

ここ、ちょっと手抜いてまして、本来なら

if(!empty($this->consumer_key) && !empty($this->consumer_secret)){

として、キーもシークレットも空じゃなければ、ってカタチにした方が正しげなんですが、言うても管理者が管理画面で見るプラグインだし、どっちかだけ書かないってコトも無いだろうといやすみませんただただ手を抜きました。。上記に書き換えました。

まぁ実装に当たっては問題ない箇所なので。。

55~84行目 API KeyとAPI SecretをTwitter側に差し出す

Twitterの検索結果って、以前はRSSとかで認証とか無くカンタンに取得できたんですが、今はAPI経由でしか取れなくなっちゃったんです。

んで、そのAPIにおいて、不特定多数のユーザーが使うようなタイプのアプリじゃないなら、一部のAPIについて回数制限を緩和したタイプを使っても良いよ!というTwitter様の心優しいご配慮が「Application-only authentication」っつー長ったらしい方法。

今回のプラグインは管理画面のダッシュボード上でしか使わないモノなので、どうせならこの方法を使うことにしました。

で、この「Application-only authentication」をPHPで使う場合、cURL関数でのやりとりがオーソドックスなようで、イメージとしては下記のような流れで検索結果の取得まで進みます。

  • 1. API KeyとAPI SecretをTwitter側に差し出す。
  • 2. TwitterからAPI使用許可証を貰う。
  • 3. API使用許可証と検索ワードをTwitter側に差し出す。
  • 4. Twitterから検索結果を貰う。

56~62行目で、cURLで通信する時のヘッダー情報を記述してます。

ポイントは60行目、API KeyとSecretをesc_attr関数でエスケープした後、それぞれをurlencode()でエンコードして、それを:記号で繋いで、それ全体をbase64_encode()でエンコード。エンコード言いたいだけちゃうで!

そして64~73行目、さっきのヘッダー情報を持ってTwitterのおうちに向かいます。

75~84行目で、レスポンスがfalseならエラー(76行目)を、レスポンス来たけど戻り値がエラーだったらエラー(81行目)を返します。

ここで出てくる _e() が、多言語用の echo みたいなヤツで、言語ファイル作って多言語化の準備が出来てれば、管理画面での設定なんかに合わせて言語を変換してくれます。

エラーが無ければ、79行目の $bearer に、Twitterから発行されたトークン(=API使用許可証)が入ってることになります。

86行目 エラーがないかの判定

ここ、もっと上手いやり方がある気がするのですが、調べる前にこんな感じで力技の分岐でやっちゃいました。。

エラーが無ければ、続いて検索結果の取得に進みます。

88~95行目 検索するワードと除外ワードをAPI用の書式に変換

TwitterのAPIに検索ワードや除外ワードを投げるために、仕様に合わせて文字列を変換します。

つっても urlencode() でエンコードした文字列をダブルクォーテーションで囲い +OR+ で繋ぐくらいですが。

89~94行目では、もし除外ワードがあったら +- で繋ぐ、と。

最後に95行目でそれをクエリに加えた問い合わせ用URLを生成してます。

96~120行目 API使用許可証と検索ワードをTwitter側に差し出す

さっきのcURLとおんなじ感じですが、100行目でトークンを貼って認証を受けたことを証明します。

103~109は通信、111~120行目もさっきと同じようにエラー処理です。

122行目

ここ、やっぱりもっと上手いやり方がある気がするのですが。。。

エラーが無ければ、取得した検索結果をダッシュボードでキレイに表示するための加工をします。

123~130行目 検索結果の文字列を整形しつつ、1つの配列にまとめる

ここはのポイントは127行目、そのままだと「Thu Mar 13 12:34:56 +0000 2015」みたいなフォーマットなので、それを「2015.03/1312:34:56」ってカタチに変換させてみました。

こっちのほうが見やすいんじゃないかなーと思うので。

133~144行目 ダッシュボードに吐き出すHTMLの記述

ここはダッシュボードに吐き出すHTMLの記述です。

141~143行目で、もし該当するツイートがなかった場合の分岐をさせてます。

147~149行目 API Key、API Secretが記入されてなかった場合

API Key、API Secretが記入されてなかった場合にたどり着く分岐です。

152行目 API KeyやAPI Secretを記述する設定フォーム用関数の宣言

よっしゃ最後、いっくぜー!

153~159行目 データが送信された時の処理

$_POSTに入ってる送信内容をWordPress独自のサニタイズ関数に通して、update_option()でそれぞれのデータをDBに保存させます。

161~164行目 設定フォームのHTML

それぞれのvalueに esc_textarea() でエスケープした各変数を出力させます。

167行目 classのインスタンス作成

さぁ!今の今まで書いてきた結果を!いざ!眼前に!示せぇぇぇぇええぇぇえぇえ!!!!

参考にした記事リスト

みなさまのお力で、ついに初の公式ディレクトリ掲載まで辿りつけました。

ありがとーございますっ!

ホント、お世話になりました♪

みんなでエゴエゴしよーぜ☆

  • このエントリーをはてなブックマークに追加

コメントを投稿する

お名前

ご連絡先メールアドレス※非公開

コメント

CAPTCHA


  • このブログのRSSを購読する
  • このブログをtwitterでつぶやく
  • このブログをFacebookで共有する
  • このブログをはてなブックマークで共有する

Contact

    お名前※必須

    ご連絡先メールアドレス

    お問い合わせ内容※必須

    CAPTCHA

    captcha

    Blog parts

    Affiliate