<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Program &#8211; My Favorites</title>
	<atom:link href="https://blog.iwh12.jp/tag/program/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.iwh12.jp</link>
	<description>writing when I feel like it. since 2017</description>
	<lastBuildDate>Sun, 21 May 2017 13:12:45 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=5.9</generator>
	<item>
		<title>Mastodon v1.3.3リリース！＋α</title>
		<link>https://blog.iwh12.jp/2017/05/07/mastodon-v1-3-3%e3%83%aa%e3%83%aa%e3%83%bc%e3%82%b9%ef%bc%81%ef%bc%8b%ce%b1/</link>
		
		<dc:creator><![CDATA[knishika]]></dc:creator>
		<pubDate>Sun, 07 May 2017 10:02:21 +0000</pubDate>
				<category><![CDATA[Mastodon]]></category>
		<category><![CDATA[Program]]></category>
		<guid isPermaLink="false">http://blog.iwh12.jp/?p=1537</guid>

					<description><![CDATA[日本時間の5月7日、GWの最終日にMastodon v1.3.3がリリースされた。早速Myインスタンスをアップデートしつつ、少し環境を整えたのでメモ書き。 ディスク容量不足！？ 何時ものようにgitでコードを更新した後、]]></description>
										<content:encoded><![CDATA[<p><img src="https://blog.iwh12.jp/wp/wp-content/uploads/2017/05/http2.jpg" alt="HTTP/2対応" width="100%" class="alignnone size-full wp-image-1538" srcset="https://blog.iwh12.jp/wp/wp-content/uploads/2017/05/http2.jpg 901w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/05/http2-300x168.jpg 300w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/05/http2-768x431.jpg 768w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/05/http2-30x17.jpg 30w" sizes="(max-width: 901px) 100vw, 901px" /><br />
日本時間の5月7日、GWの最終日にMastodon v1.3.3がリリースされた。早速Myインスタンスをアップデートしつつ、少し環境を整えたのでメモ書き。<span id="more-1537"></span></p>
<h3>ディスク容量不足！？</h3>
<p>何時ものようにgitでコードを更新した後、docker-compose buildを実行すると途中でerror。原因は容量不足で書き込めない。dfで調べると確かにもう99%使っている。</p>
<p>考えてみればテストも兼ね何度もDockerをup/downしているので、不要なコンテナとイメージが蓄積され、ほぼ容量を使い切っている状態だった。</p>
<p>とりあえず綺麗さっぱり忘れるために以下のコマンドを実行。大幅に容量が増えたのを確認。</p><pre class="crayon-plain-tag"># コンテナの削除
$ docker ps -aq | xargs docker rm
# イメージの削除
$ docker images -aq | xargs docker rmi</pre><p></p>
<h3>HTTP/2対応へ</h3>
<p>アップデートが終わり、v1.3.3を起動しTootを眺めていると、HTTP/2の文字が。「そう言えばまだやっていなかったな…。」と対応することに。</p>
<p>ただCentOS 7で普通にyumすると、OpenSSLはv1.0.1系が入るものの、HTTP/2にするにはv1.0.2系が必要。更にnginxがそれを参照しなければならない。と言うことで面倒だがmake決定。</p>
<p>CentOS 7はminimalを使った関係上、開発環境が入っていない。以下必要なコマンドなどをインストール。</p><pre class="crayon-plain-tag">yum -y groupinstall 'Development Tools'
yum -y install wget openssl-devel libxml2-devel libxslt-devel gd-devel perl-ExtUtils-Embed GeoIP-devel rpmdevtools</pre><p>次に/usr/local/srcでOpenSSLコードを展開。makeして、バイナリを入れ替える。</p><pre class="crayon-plain-tag"># cd /usr/local/src
# wget https://www.openssl.org/source/openssl-1.0.2k.tar.gz
# tar zxvf openssl-1.0.2k.tar.gz
# cd openssl-1.0.2k
# ./config shared -fPIC
# make
# make test
# make install

# cd /usr/bin/
# cp /usr/bin/openssl /usr/bin/openssl~
# rm -f /usr/bin/openssl
# ln -s /usr/local/ssl/bin/openssl

# openssl version
OpenSSL 1.0.2k  26 Jan 2017</pre><p>今度はnginx。事前に./configureに指定するパラメータを現バージョンでメモしておく（configure arguments:）。</p><pre class="crayon-plain-tag"># nginx -V
nginx version: nginx/1.12.0
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-4) (GCC) 
built with OpenSSL 1.0.1e-fips 11 Feb 2013
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib64/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log
.
.
--with-ld-opt='-Wl,-z,relro -Wl,-z,now -pie'</pre><p>以降は普通のmake。先にメモした./configureのオプション最後に、</p><pre class="crayon-plain-tag">--with-openssl=/usr/local/src/openssl-1.0.2k \
--with-openssl-opt='-fPIC'</pre><p>この2つを加えて./configureする。</p><pre class="crayon-plain-tag"># cd /usr/local/src
# wget https://nginx.org/download/nginx-1.13.0.tar.gz
# tar zxvf nginx-1.13.0.tar.gz
# cd nginx-1.13.0

# ./configure \
--prefix=/etc/nginx \
--sbin-path=/usr/sbin/nginx \
--modules-path=/usr/lib64/nginx/modules \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
.
.
--with-openssl=/usr/local/src/openssl-1.0.2k \
--with-openssl-opt='-fPIC'

# make
# make install

# nginx -V
nginx version: nginx/1.13.0
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-11) (GCC) 
built with OpenSSL 1.0.2k  26 Jan 2017
TLS SNI support enabled</pre><p>既に.confには</p>
<p>server {<br />
listen 443 ssl http2;<br />
listen [::]:443 ssl http2;</p>
<p>http2の記述があるのでnginxをリスタートするだけでいい。</p>
<p>確認はChromeの検証でNetwork / Protocolがh2になっているか（冒頭の画面キャプチャ）、nginxのaccess.logでHTTP/2.0の文字が見えればOKだ。</p>
<p>追記：.confのサンプルではHSTSの設定がこうなっているが、</p>
<p>add_header Strict-Transport-Security &#8220;max-age=31536000&#8221;;</p>
<p>サブドメインでインスタンスを運用する時には、includeSubdomainsを追加しないと効いてない感じだ。</p>
<p>add_header Strict-Transport-Security &#8220;max-age=31536000; includeSubdomains&#8221;;</p>
<h3>Qualys SSL LABSのスコアがA-！？</h3>
<p>いろいろ変わったので<a href="https://www.ssllabs.com/ssltest/analyze.html" target="_blank" class="external ext_icon">再度チェック</a>したところ、v1.2系でインスタンスを上げた時はA+だったのに何故かA-に落ちてしまった。違いは…と考えてみるとnginxの.confが一部異なっている。</p><pre class="crayon-plain-tag"># これだとA-になる
ssl_ciphers HIGH:!MEDIUM:!LOW:!aNULL:!NULL:!SHA!RC4;

# これでA+
ssl_ciphers EECDH+AESGCM:EECDH+AES;
ssl_ecdh_curve prime256v1;</pre><p><a href="https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Production-guide.md" target="_blank" class="external ext_icon">ここのサンプル</a>をそのまま使うとA-になるので、当初の内容へ戻すとA+に。これで一件落着！</p>
<p><img src="https://blog.iwh12.jp/wp/wp-content/uploads/2017/05/40222c2d17a3d8aa6318509c8a2b76b9.jpg" alt="Qualys SSL LABS" width="100%" class="alignnone size-full wp-image-1541" srcset="https://blog.iwh12.jp/wp/wp-content/uploads/2017/05/40222c2d17a3d8aa6318509c8a2b76b9.jpg 1000w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/05/40222c2d17a3d8aa6318509c8a2b76b9-300x128.jpg 300w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/05/40222c2d17a3d8aa6318509c8a2b76b9-768x328.jpg 768w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/05/40222c2d17a3d8aa6318509c8a2b76b9-30x13.jpg 30w" sizes="(max-width: 1000px) 100vw, 1000px" /></p>
<p>余談になるが今回、安定版の1.3-stableと先行版にBranchした。<a href="http://www.itmedia.co.jp/news/articles/1705/03/news024.html" target="_blank" class="external ext_icon">新機能の購読言語指定</a>などはこの1.3-stableには入っていない。</p>
<p>しかし比較的天候に恵まれたGWに何をやってるのか（笑）</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Mastodon、タイムラインに記事やTwitter、YouTubeなどのサムネイルを表示する方法（勝手改造要注意）</title>
		<link>https://blog.iwh12.jp/2017/04/30/mastodon%e3%80%81%e3%82%bf%e3%82%a4%e3%83%a0%e3%83%a9%e3%82%a4%e3%83%b3%e3%81%ab%e8%a8%98%e4%ba%8b%e3%82%84twitter%e3%80%81youtube%e3%81%aa%e3%81%a9%e3%81%ae%e3%82%b5%e3%83%a0%e3%83%8d%e3%82%a4/</link>
		
		<dc:creator><![CDATA[knishika]]></dc:creator>
		<pubDate>Sun, 30 Apr 2017 03:24:56 +0000</pubDate>
				<category><![CDATA[Mastodon]]></category>
		<category><![CDATA[Program]]></category>
		<guid isPermaLink="false">http://blog.iwh12.jp/?p=1442</guid>

					<description><![CDATA[Mastodonのタイムラインを眺めていると、インスタンスにもよるだろうが、文字ばかりで少し寂しい。Tootの詳細表示では、記事やTwitter、Instagram、YouTubeなど、リンクへのサムネイル＝Cardが表]]></description>
										<content:encoded><![CDATA[<p><img src="https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/mastodon_timeline.jpg" alt="Mastodon、タイムラインに記事やTwitter、YouTubeなどのサムネイルを表示する方法（勝手改造要注意）" width="100%" class="alignnone size-full wp-image-1443" srcset="https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/mastodon_timeline.jpg 985w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/mastodon_timeline-300x182.jpg 300w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/mastodon_timeline-768x467.jpg 768w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/mastodon_timeline-30x18.jpg 30w" sizes="(max-width: 985px) 100vw, 985px" />Mastodonのタイムラインを眺めていると、インスタンスにもよるだろうが、文字ばかりで少し寂しい。Tootの詳細表示では、記事やTwitter、Instagram、YouTubeなど、リンクへのサムネイル＝Cardが表示できるので、これをタイムラインにも欲しいところ。そこでコードを追ってみた。<span id="more-1442"></span></p>
<h3>タイムラインに手を加えるには</h3>
<p>ログイン後の3カラムの画面、あれは全て自らのAPIで値を取得し、JavaScriptで書いている。例えば、</p>
<p><strong>連合タイムライン</strong><br />
インスタンスURL/api/v1/timelines/public<br />
<strong>ローカルタイムライン</strong><br />
インスタンスURL/api/v1/timelines/public?local=true</p>
<p>をブラウザへ入力すると、文字の羅列が表示されると思う。JSONと呼ばれるフォーマットだ。画面描画はこれを元にしている（iOSやAndroidのアプリも同様）。ホームはTokenが必要なので少し細工が必要になるものの、出てくるフォーマット自体は同じだ。</p>
<p>となると、このJSONの中にCardの情報が入っていない場合、タイムラインには表示できない。が、残念ながら添付画像情報のmedia_attachmentsはあるものの、必要とするpreview_cardは無い(どちらもDBのTable名)。</p>
<p>従って改造その1はJSONにpreview_cardを加える作業となる。当然JSONの中身が変わるので、アプリなどの互換性が気になると思うが、（例外もあるかも知れないが）一般的に知らない項目が増えても無視するだけで作動には問題無い。</p>
<p>ただデータ転送量が若干増えるので、微妙に反応が鈍くなる可能性はある。</p>
<h3>JSONへpreview_cardを追加する</h3>
<p>タイムラインAPIのcontrollerは、</p>
<p>app/controllers/api/v1/timelines_controller.rb</p>
<p>これだ。Status.as_public_timeline()など、modelsのstatus.rbにあるclassを呼んでいる。しかしAPIに関しては、status.rbの中を見てもDBのstatuses Table（Tootの中身）しか実質扱っていない。</p>
<p>別Tableになっているmedia_attachmentsをどこで引っ張ているのか…と、しばし悩んだところ該当箇所を発見。</p>
<p>app/views/api/v1/statuses/_show.rabl</p>
<p>ここで出す直前にmedia_attachmentsをJSONへマージしている。つまりここへpreview_cardを追加すればいい。</p><pre class="crayon-plain-tag"># app/views/api/v1/statuses/_show.rablへ追加
child :preview_card, object_root: false do
  extends 'api/v1/statuses/_card'
end</pre><p>実体は新規で_card.rablを作り中は以下のようになる。</p><pre class="crayon-plain-tag"># app/views/api/v1/statuses/_card.rablを新規作成
attributes :url, :title, :description, :type,
           :author_name, :author_url, :provider_name,
           :provider_url, :html, :width, :height

node(:image) { |card| card.image? ? full_asset_url(card.image.url(:original)) : nil }</pre><p>これで一旦build/assets:precompileをし直し、先のAPIをブラウザで入力すると、</p><pre class="crayon-plain-tag">"preview_card":{"url":"https://www.youtube.com/watch?v=2tvooAF67z0","title":"BABYMETAL Kami Band Intro Awadama Fever Columbia SC 2017","description":"BABYMETAL Kami Band Intro Awadama Fever Opening for RHCP Columbia SC 2017","type":"link","author_name":"","author_url":"","provider_name":"","provider_url":"","html":"","width":0,"height":0,"image":"http://192.168.11.205:3000/system/preview_cards/images/000/000/016/original/maxresdefault.jpg?1492667166"},</pre><p>preview_cardが追加されているはずだ(Cardが無い時は&#8221;preview_card:null&#8221;のみ)。これで準備は完了。改造その2は、画面側の追加プログラムとなる。</p>
<h3>タイムラインへCardを表示する</h3>
<p>詳しい話を書くと長くなるので単刀直入に（笑）。詳細表示のCardはここで書いている。</p>
<p>app/assets/javascripts/components/features/status/containers/card_container.jsx<br />
app/assets/javascripts/components/features/status/components/card.jsx</p>
<p>二番目が実際に表示している部分。調べたところタイムラインにも共通で使えそうだ。従ってタイムラインからcard.jsxを呼ぶcard_container_tl.jsxを新規で作成する。</p><pre class="crayon-plain-tag"># app/assets/javascripts/components/features/status/containers/card_container_tl.jsxを新規作成
import { connect } from 'react-redux';
import Card from '../components/card';

const mapStateToProps = (state,{status}) =&gt; ({
  card: status.getIn(['preview_card'], null)
});

export default connect(mapStateToProps)(Card);</pre><p>次に実際タイムラインを描画している部分</p>
<p>app/assets/javascripts/components/components/status.jsx</p>
<p>へ、以下を追加する。一番目はコードの初めにimportの山があるので、そこへ挿入。二番目は、コードの最後の方に{media}だけ書かれた行があるので、その横に追記する。</p><pre class="crayon-plain-tag">import CardContainer from '../features/status/containers/card_container_tl';

&lt;CardContainer status={status} /&gt;</pre><p>これで準備が出来たので、再度build/assets:precompileすればOK。タイムラインにCardが表示されたら正解だ。</p>
<p>現状の問題点は2つ。</p>
<ol style="padding-left: 1em;">
<li>ローカルインスタンス内でしかCardが表示されない（但し、これは非改造のv1.3.2でも外部インスタンスは詳細に出ないので同じ症状）</li>
<li>リンク先の情報を取得するタイミングが詳細を表示した時なので、誰かが詳細を見ない限り、タイムラインにCardが表示されない</li>
</ol>
<p>1)はもともと外インスタンスのCardは詳細でも出ないので、システム側の問題だと思われる。2)は投稿した時に自分で確認しつつ詳細を見るのが一番手っ取り早い（笑）。一度詳細を表示すれば情報がDBへ入る。</p>
<p>Card用のデータをFetchするコードは</p>
<p>app/services/fetch_link_card_service.rb</p>
<p>ここにある。以降はまだコードを追ってないので不明。sidekiqのWorkerにもlink_crawl_worker.rbが入っている。</p>
<p>いずれにしてもこれで少しはタイムラインが賑やかになる。ただこれらの改造はブラウザからのアクセスのみ有効で、残念ながら勝手改造を関知しないアプリは無関係だ。</p>
<p><strong>またこの改造によって何か不都合が発生しても責任は持てないので自己責任</strong>でお願いしたい。本家に同等の機能が付けば嬉しいのだが…。</p>
<p>追記1：1)の件はBUGのようだ。”<a href="https://github.com/tootsuite/mastodon/issues/2572" target="_blank" class="external ext_icon">cards only get created for local posts</a>”<br />
追記2：v1.4系では後半の表示系PATHが変わった（内容は同じ）。<br />
app/javascript/mastodon/features/status/containers/card_container_tl.js<br />
app/javascript/mastodon/components/status.js</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Mastodon、タグ付きでDMするとネットからのタグ一覧表示で見えてしまう問題の修正方法</title>
		<link>https://blog.iwh12.jp/2017/04/23/mastodon%e3%80%81%e3%82%bf%e3%82%b0%e4%bb%98%e3%81%8d%e3%81%a7dm%e3%81%99%e3%82%8b%e3%81%a8%e3%83%8d%e3%83%83%e3%83%88%e3%81%8b%e3%82%89%e3%81%ae%e3%82%bf%e3%82%b0%e4%b8%80%e8%a6%a7%e8%a1%a8%e7%a4%ba/</link>
		
		<dc:creator><![CDATA[knishika]]></dc:creator>
		<pubDate>Sun, 23 Apr 2017 09:31:19 +0000</pubDate>
				<category><![CDATA[Mastodon]]></category>
		<category><![CDATA[Program]]></category>
		<guid isPermaLink="false">http://blog.iwh12.jp/?p=1415</guid>

					<description><![CDATA[Mastodonで、公開範囲をUnlisted、Private、Directにして、タグ付きでTootすると、ネットからのタグ一覧（インスタンスURL/tags/タグ名）で見えてしまうBUG（これは明らかにBUGだと思う]]></description>
										<content:encoded><![CDATA[<p><img src="https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/mastodon_tag_bug.jpg" alt="MastodonのTag BUG" width="100%" class="alignnone size-full wp-image-1416" srcset="https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/mastodon_tag_bug.jpg 937w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/mastodon_tag_bug-300x133.jpg 300w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/mastodon_tag_bug-768x340.jpg 768w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/mastodon_tag_bug-30x13.jpg 30w" sizes="(max-width: 937px) 100vw, 937px" /><br />
Mastodonで、公開範囲をUnlisted、Private、Directにして、タグ付きでTootすると、ネットからのタグ一覧（インスタンスURL/tags/タグ名）で見えてしまうBUG（これは明らかにBUGだと思う）の修正方法。<span id="more-1415"></span></p>
<p>controllers/tags_controller.rbの</p>
<p></p><pre class="crayon-plain-tag"># frozen_string_literal: true

class TagsController &lt; ApplicationController
  layout 'public'

  def show
    @tag      = Tag.find_by!(name: params[:id].downcase)
    @statuses = @tag.statuses.order('id desc').paginate_by_max_id(20, params[:max_id]) #####
    @statuses = cache_collection(@statuses, Status)
  end
end</pre><p>この#####の行を</p><pre class="crayon-plain-tag">@statuses = @tag.statuses.order('id desc').where(visibility: [:public]).paginate_by_max_id(20, params[:max_id])</pre><p>これに変更する。つまり対象をvisibility: [:public]として、一覧に表示するTootを絞っている。</p>
<p>コード修正後のインスタンスの起動は先に書いた<a href="https://blog.iwh12.jp/2017/04/23/mastodon、ネットからprofileを見た時にunlistedが一覧に/">ここ</a>の後半を参考にして欲しい。既にMyインスタンスは修正済みで作動している。</p>
<p>Unlistedとタグ付きDMのネットからの一覧表示問題も片付いたので、これで少しは安心できるかな！？</p>
<p>追記1：参考までにログイン後のHome画面（3つTLが並ぶ画面）では、先のUnlistedも含め問題は発生しない。理由は使っているロジックが違うから。自らAPIを呼んで描画している。</p>
<p>インスタンスURL/api/v1/timelines/public/<br />
インスタンスURL/api/v1/timelines/tag/mastodon</p>
<p>この様に呼ぶとJSONが得られる。同じ方法でネット側（非ログイン系）の画面も作れると思うのだが、何故この様な仕様になっているかは不明だ。</p>
<p>追記2：このBUGはcommit済みとのこと。次のリリースまで待てない時に参考にして欲しい。</p>
<blockquote class="twitter-tweet" data-conversation="none" data-cards="hidden" data-partner="tweetdeck">
<p lang="ja" dir="ltr"><a href="https://twitter.com/rokuzouhonda" target="_blank" class="external ext_icon">@rokuzouhonda</a> これは <a href="https://t.co/F6uZPConTA" target="_blank" class="external ext_icon">https://t.co/F6uZPConTA</a> で報告されて、2日前のコミットで修正されたようなので、次のリリースでは直っているかと思います</p>
<p>&mdash; masayoshi takahashi (@takahashim) <a href="https://twitter.com/takahashim/status/856103271261282304" target="_blank" class="external ext_icon">April 23, 2017</a></p></blockquote>
<p><script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script></p>
<p>追記3：v1.3系で修正済み</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Mastodon、ネットからProfileを見た時にUnlistedが一覧に出てしまう問題の修正方法</title>
		<link>https://blog.iwh12.jp/2017/04/23/mastodon%e3%80%81%e3%83%8d%e3%83%83%e3%83%88%e3%81%8b%e3%82%89profile%e3%82%92%e8%a6%8b%e3%81%9f%e6%99%82%e3%81%abunlisted%e3%81%8c%e4%b8%80%e8%a6%a7%e3%81%ab/</link>
		
		<dc:creator><![CDATA[knishika]]></dc:creator>
		<pubDate>Sun, 23 Apr 2017 01:12:18 +0000</pubDate>
				<category><![CDATA[Mastodon]]></category>
		<category><![CDATA[Program]]></category>
		<guid isPermaLink="false">http://blog.iwh12.jp/?p=1389</guid>

					<description><![CDATA[MastodonにはPublic、Unlisted、Private、Direct…と、4つの公開オプションがあるのだが、何故かUnlistedにしても、ネットからProfileをアクセス（インスタンスURL/@ユーザーI]]></description>
										<content:encoded><![CDATA[<p><img src="https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/mastodon_unlisted.jpg" alt="mastodon unlisted bug" width="100%" class="alignnone size-full wp-image-1391" srcset="https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/mastodon_unlisted.jpg 801w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/mastodon_unlisted-300x131.jpg 300w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/mastodon_unlisted-768x335.jpg 768w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/mastodon_unlisted-30x13.jpg 30w" sizes="(max-width: 801px) 100vw, 801px" />MastodonにはPublic、Unlisted、Private、Direct…と、4つの公開オプションがあるのだが、何故かUnlistedにしても、ネットからProfileをアクセス（インスタンスURL/@ユーザーID）すると、Toot一覧に出てしまう問題がある。仕様かBUGかは不明だが気持ち悪いので修正したい。方法は…。<span id="more-1389"></span></p>
<p>Mastodon本体はRuby on Railsで書かれており、昔やったな…と思いながらコードを探すと、controllersのaccounts_controller.rbに</p><pre class="crayon-plain-tag">def show
    respond_to do |format|
      format.html do
        @statuses = @account.statuses.permitted_for(@account, current_account).order('id desc').paginate_by_max_id(20, params[:max_id], params[:since_id])
        @statuses = cache_collection(@statuses, Status)
      end
.
.
.</pre><p>を発見。どうやらmodelsのpermitted_for()でデータを取得しているようだ。該当箇所は、status.rbのここ。</p><pre class="crayon-plain-tag">def permitted_for(target_account, account)
    return where.not(visibility: [:private, :direct]) if account.nil?</pre><p>account.nil?つまり非ログイン時、privateとdirectだけ除外している。ここへunlistedを加え、</p>
<p></p><pre class="crayon-plain-tag">def permitted_for(target_account, account)
    return where.not(visibility: [:unlisted, :private, :direct]) if account.nil?</pre><p>こうすればいい（外に何も出したくない時は:publicも加えればいい）。</p>
<p>ただDockerからシステムを剥がして運用している場合は修正するだけでいいのだが、docker-compose up -dで起動している場合は、一旦downして再度buildしないとこの修正が反映されない。</p>
<p>docker-compose downするのはちょっと嫌なので（笑）、直接コンテナの中を触ることにする。</p><pre class="crayon-plain-tag">$ docker-compose ps
        Name                      Command               State 
--------------------------------------------------------------
mastodon_db_1          docker-entrypoint.sh postgres    Up    
mastodon_redis_1       docker-entrypoint.sh redis ...   Up    
mastodon_sidekiq_1     bundle exec sidekiq -q def ...   Up    
mastodon_streaming_1   npm run start                    Up    
mastodon_web_1         bundle exec rails s -p 300 ...   Up</pre><p>ログインする名前を探す（ここではmastodon_web_1）。</p>
<p>$ docker exec -it mastodon_web_1 ash</p>
<p>でログインできるので、該当コードを修正（viが使える）。</p>
<p>$ docker-compose stop<br />
$ docker-compose start</p>
<p>これでコードが反映される。既にMyインスタンスは修正済だ。</p>
<p>余談になるが、Privateで写真を添付した場合でも、写真のURLが分かればネットから抜けてしまう（大昔のmixiもそうだった）。</p>
<p>真面目に対応するのは結構面倒で且つDBに負荷がかかるので（画像を表示する度に相手との関係を調べる必要があるのと、URLではアクセスできないPATHへ画像を置き、表示する時はリダイレクトしなければならない）、Mastodonが対応するのは望み薄だろう。</p>
<p>追記：この件、どうやら<a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/User-guide.md#toot-privacy" target="_blank" class="external ext_icon">意図的</a>らしいので、pullリクしても駄目な雰囲気。各インスタンスで個別対応だろうか！？</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Mastodonのインスタンスを上げてみた</title>
		<link>https://blog.iwh12.jp/2017/04/19/mastodon%e3%81%ae%e3%82%a4%e3%83%b3%e3%82%b9%e3%82%bf%e3%83%b3%e3%82%b9%e3%82%92%e4%b8%8a%e3%81%92%e3%81%a6%e3%81%bf%e3%81%9f/</link>
		
		<dc:creator><![CDATA[knishika]]></dc:creator>
		<pubDate>Wed, 19 Apr 2017 05:18:37 +0000</pubDate>
				<category><![CDATA[Mastodon]]></category>
		<category><![CDATA[Program]]></category>
		<guid isPermaLink="false">http://blog.iwh12.jp/?p=1373</guid>

					<description><![CDATA[あっと言う間に爆発しているMastodon。面白そうなので、早速インストールしてインスタンスを公開した。その手順全てを紹介。 使用OS CentOS 7(Minimal ISO)、メモリ1GB、2 Core ※実際運用し]]></description>
										<content:encoded><![CDATA[<p><img src="https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/mastodon.jpg" alt="Mastodon" width="100%" class="alignnone size-full wp-image-1374" srcset="https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/mastodon.jpg 1024w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/mastodon-300x187.jpg 300w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/mastodon-768x478.jpg 768w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/mastodon-30x19.jpg 30w" sizes="(max-width: 1024px) 100vw, 1024px" />あっと言う間に爆発しているMastodon。面白そうなので、早速インストールしてインスタンスを公開した。その手順全てを紹介。<span id="more-1373"></span></p>
<h4>使用OS</h4>
<p>CentOS 7(Minimal ISO)、メモリ1GB、2 Core<br />
※実際運用してみると、メモリは2GBあった方が良さそう</p>
<h4>インストール手順</h4>
<p>1)/etc/sysconfig/network-scripts/ifcfg-eth0<br />
※ネットワーク周りに合わせて設定</p>
<p>2)DNSへインスタンスのIN Aなどを記述</p>
<p>3)yum update、gitなど必要なコマンドをインストール</p><pre class="crayon-plain-tag"># yum update
# yum -y install git
# yum install bind-utils
# yum install net-tools</pre><p></p>
<p>4)firewallの設定</p><pre class="crayon-plain-tag"># firewall-cmd --add-port=80/tcp --zone=public --permanent
# firewall-cmd --add-port=443/tcp --zone=public --permanent
# firewall-cmd --reload</pre><p></p>
<p>5)SELinuxをdisabled</p><pre class="crayon-plain-tag"># vi /etc/sysconfig/selinux
SELINUX=disabled
# reboot</pre><p></p>
<p>6)SSL証明書の取得</p><pre class="crayon-plain-tag">$ git clone https://github.com/letsencrypt/letsencrypt
$ cd letsencrypt
$ ./letsencrypt-auto certonly --standalone -d xxxx.xxxx.jp
※xxxx.xxxx.jpは使用するドメイン名（事前に有効なのを確認）
※nginx側は特に設定する必要無し</pre><p></p>
<p>と、先にオーソドックスなネットワーク関連の設定を済ます。この辺りは他のサーバーでも同じ。</p>
<p>7)Mastodonをclone</p><pre class="crayon-plain-tag">$ git clone --depth=1 -b v1.3.2 https://github.com/tootsuite/mastodon.git</pre><p></p>
<p>8)nginxをインストール</p><pre class="crayon-plain-tag">$ sudo vi /etc/yum.repos.d/nginx.repo
[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/centos/7/$basearch/
gpgcheck=0
enabled=1

$ sudo yum -y --enablerepo=nginx install nginx</pre><p></p>
<p>9)docker-ceをインストール</p><pre class="crayon-plain-tag">$ sudo yum -y install yum-utils
$ sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
$ sudo yum install docker-ce

$ sudo groupadd docker
$ sudo usermod -aG docker $USER
$ sudo service docker start
$ sudo chkconfig docker on
$ sudo reboot
※この一連を行わないとdocker-composeが作動せず</pre><p></p>
<p>10)docker-composeをインストール</p><pre class="crayon-plain-tag"># curl -L https://github.com/docker/compose/releases/download/1.12.0/docker-compose-`uname -s`-`uname -m` &gt; /usr/local/bin/docker-compose
# chmod +x /usr/local/bin/docker-compose</pre><p></p>
<p>11)Mastodonの設定</p><pre class="crayon-plain-tag">$ cp .env.production.sample .env.production
$ vi .env.production
LOCAL_DOMAIN=xxxx.xxxx.jp
LOCAL_HTTPS=true

SMTP_SERVER=smtp.sparkpostmail.com
SMTP_PORT=587
SMTP_LOGIN=SMTP_Injection
SMTP_PASSWORD=xxxxxxxxxxxxxxxxxxxxx
SMTP_FROM_ADDRESS=xxxx@xxxx.xxxx.jp
SMTP_AUTH_METHOD=plain
SMTP_OPENSSL_VERIFY_MODE=none
SMTP_ENABLE_STARTTLS_AUTO=true
※SMTP_PASSWORDはhttps://app.sparkpost.com/dashboardで得たキー
※サイト認証用にDNSへscphxxxx._domainkey.xxxx.xxxx.jp. IN TXT &quot;v=DKIM1;...&quot;を追加

$ docker-compose run --rm web rake secret
※三回実行し得たキーを下記へコピペ。一回目は時間がかかる
$ vi .env.production
PAPERCLIP_SECRET=
SECRET_KEY_BASE=
OTP_SECRET=

$ vi docker-compose.yml
 db:
    restart: always
    image: postgres:alpine
### Uncomment to enable DB persistance
    volumes:
      - ./postgres:/var/lib/postgresql/data

  redis:
    restart: always
    image: redis:alpine
### Uncomment to enable REDIS persistance
    volumes:
      - ./redis:/data
※インスタンスをdownした後でもTootやアカウントを残すため、DB関連のコメントアウトを外す</pre><p></p>
<p>smtpは、<a href="https://www.sparkpost.com/" target="_blank" class="external ext_icon">SparkPost</a>というサービスを利用。無料の範囲では送信可能な件数が限られるものの、今回の用途程度なら問題無さそう。サイト確認用のDNSによる認証があるので要注意（これに気付かずハマった）。</p>
<p>12)nginxの設定</p><pre class="crayon-plain-tag">$ sudo vi /etc/nginx/conf.d/mastodon.conf
map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}

server {
  listen 80;
  listen [::]:80;
  server_name xxxx.xxxx.jp;
  return 301 https://$host$request_uri;
}

server {
  listen 443 ssl;
  listen [::]:443 ssl;
  server_name xxxx.xxxx.jp;

  ssl_protocols TLSv1.2;
  ssl_ciphers EECDH+AESGCM:EECDH+AES;
  ssl_ecdh_curve prime256v1;
  ssl_prefer_server_ciphers on;
  ssl_session_cache shared:SSL:10m;

  ssl_certificate     /etc/letsencrypt/live/xxxx.xxxx.jp/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/xxxx.xxxx.jp/privkey.pem;

  keepalive_timeout    70;
  sendfile             on;
  client_max_body_size 0;

  root /home/mastodon/live/public;

  gzip on;
  gzip_disable &quot;msie6&quot;;
  gzip_vary on;
  gzip_proxied any;
  gzip_comp_level 6;
  gzip_buffers 16 8k;
  gzip_http_version 1.1;
  gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

  add_header Strict-Transport-Security &quot;max-age=31536000&quot;;

  location / {
    try_files $uri @proxy;
  }

  location @proxy {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header Proxy &quot;&quot;;
    proxy_pass_header Server;

    proxy_pass http://127.0.0.1:3000;
    proxy_buffering off;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    tcp_nodelay on;
  }

  location /api/v1/streaming {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header Proxy &quot;&quot;;

    proxy_pass http://localhost:4000;
    proxy_buffering off;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    tcp_nodelay on;
  }

  error_page 500 501 502 503 504 /500.html;
}
※xxxx.xxxx.jpを使用するドメイン名に書き換える</pre><p>mastodon.confは公式の<a href="https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Production-guide.md" target="_blank" class="external ext_icon">ここ</a>にあるので、example.comの部分のみ書き換えればOKだ。</p>
<p>最近、少し内容が変わり、 ssl_dhparamが追加された。事前に</p><pre class="crayon-plain-tag">sudo openssl dhparam 2048 -out /etc/ssl/certs/dhparam.pem</pre><p>を実行すること。</p>
<p>13)起動</p><pre class="crayon-plain-tag">$ sudo systemctl start nginx
$ sudo systemctl enable nginx

$ docker-compose build
$ docker-compose run --rm web rails db:migrate
$ docker-compose run --rm web rails assets:precompile
$ docker-compose up -d

$ docker-compose run --rm web rails mastodon:make_admin USERNAME=xxxxx
※USERNAME=は管理アカウントのusername</pre><p>仕上げは、dailyメンテナンス用のコマンドを1日1回（以上）叩くよう、以下をcrontabに仕込む。</p><pre class="crayon-plain-tag">docker-compose run --rm web rake mastodon:daily</pre><p></p>
<p>ググりながら、インストールしながらメモしたのをそのままコピペしているので大丈夫と思うが、間違っていたら申し訳ない。</p>
<p>身内との遊び用なのでここではURLを掲載しないものの、連合タイムラインに載るのでばれるな（笑）</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Windows 10 Creators Updateで「Bash」がバージョンアップ【後編】（補足説明あり）</title>
		<link>https://blog.iwh12.jp/2017/04/18/windows-10-creators-update%e3%81%a7%e3%80%8cbash%e3%80%8d%e3%81%8c%e3%83%90%e3%83%bc%e3%82%b8%e3%83%a7%e3%83%b3%e3%82%a2%e3%83%83%e3%83%97%e3%80%90%e5%be%8c%e7%b7%a8%e3%80%91%ef%bc%88%e8%a3%9c/</link>
		
		<dc:creator><![CDATA[knishika]]></dc:creator>
		<pubDate>Mon, 17 Apr 2017 21:01:42 +0000</pubDate>
				<category><![CDATA[記事]]></category>
		<category><![CDATA[PC Watch]]></category>
		<category><![CDATA[Program]]></category>
		<guid isPermaLink="false">http://blog.iwh12.jp/?p=1324</guid>

					<description><![CDATA[記事の最後で同期と非同期について触れているものの、詳細は書いていないので、こちらでフォローしたい。まず「実は単に繋げるだけではうまくいかない。」この部分をテスト。 単純にMongoDBへのロードと検索を繋げると以下のよう]]></description>
										<content:encoded><![CDATA[<p><article class="shortcode_share"><figure><a href="http://pc.watch.impress.co.jp/docs/column/nishikawa/1055234.html" target="_blank" class="external"><img src="http://pc.watch.impress.co.jp/img/pcw/list/1055/234/00.jpg" alt="image" class="share_pict" border="0" width="100%" /></a></figure><div class="share_container"><blockquote><p>“筆者のブログは、WordPress REST APIからみでいろいろプログラミングネタを載せているが、さすがにここでそのまま扱うには場違いのような気がするので、まずNoSQLのMongoDBで遊んでみたい。“</p></blockquote></div></article><span id="more-1324"></span></p>
<p>記事の最後で同期と非同期について触れているものの、詳細は書いていないので、こちらでフォローしたい。まず「実は単に繋げるだけではうまくいかない。」この部分をテスト。</p>
<p>単純にMongoDBへのロードと検索を繋げると以下のようになるだろうか（以降、コピペで作動。ただしエラーチェックなどは含まず）。</p><pre class="crayon-plain-tag">var mongoClient = require('mongodb').MongoClient;
var mongodb = 'mongodb://localhost:27017/test';

console.log('json read start');
mongoClient.connect(mongodb, function(err, db) {
    var http = require('http');

    http.get('http://blog.iwh12.jp/test/tags.json', (json) =&gt; {
        var body = '';
        json.setEncoding('utf8');

        json.on('data', (chunk) =&gt; {
            body += chunk;
        });

        json.on('end', () =&gt; {
            var d = JSON.parse(body); 
            db.collection('tags').insertMany(d).then(function(err, r) {
                db.close();
                console.log('json read end');
            });
        });
    });
});

console.log('search start');
mongoClient.connect(mongodb, function(err, db) {
    db.collection('tags').find({}).toArray(function (err, tags) {
        console.log(tags);
        db.close();
        console.log('search end');
        process.exit(0);
    }); 
});</pre><p>これを実行すると…。</p>
<p>$ node test1.js<br />
json read start<br />
search start<br />
[]<br />
search end</p>
<p>やはり&#8217;json read end&#8217;の文字がどこにも無く、検索結果は何も表示されない。これはMongoDBへのinsertMany()が非同期で行われるためで、「insertMany()宜しくね！」で丸投げして、結果を待たず先に進んでしまうからだ。</p>
<p>insertMany()が正常に終わった後、検索するには以下のようになる（見易いよう、検索側を関数にしている）。</p><pre class="crayon-plain-tag">var mongoClient = require('mongodb').MongoClient;
var mongodb = 'mongodb://localhost:27017/test';

console.log('json read start');
mongoClient.connect(mongodb, function(err, db) {
    var http = require('http');

    http.get('http://blog.iwh12.jp/test/tags.json', (json) =&gt; {
        var body = '';
        json.setEncoding('utf8');

        json.on('data', (chunk) =&gt; {
            body += chunk;
        });

        json.on('end', () =&gt; {
            var d = JSON.parse(body); 
            db.collection('tags').insertMany(d).then(function(err, r) {
                db.close();
                console.log('json read end');
                dbSearch();
            });
        });
    });
});

function dbSearch() {
    console.log('search start');
    mongoClient.connect(mongodb, function(err, db) {
        db.collection('tags').find({}).toArray(function (err, tags) {
            console.log(tags);
            db.close();
            console.log('search end');
            process.exit(0);
        }); 
    });
}</pre><p>実行結果は、</p>
<p>$ node test2.js<br />
json read start<br />
json read end<br />
search start<br />
[ { _id: 58f29ed0b0e7000078daf12d,<br />
    id: 21,<br />
    count: 3,<br />
    description: &#8221;,<br />
    link: &#8216;https://blog.iwh12.jp/tag/2in1/&#8217;,<br />
    name: &#8216;2in1&#8217;,<br />
    slug: &#8216;2in1&#8217;,<br />
    taxonomy: &#8216;post_tag&#8217;,<br />
    meta: [],<br />
    _links:<br />
     { self: [Object],<br />
       collection: [Object],<br />
       about: [Object],<br />
       &#8216;wp:post_type&#8217;: [Object],<br />
       curies: [Object] } },<br />
.<br />
. // 結果の表示<br />
.<br />
search end</p>
<p>と、&#8217;json read end&#8217;と結果が表示される。一般的に非同期で値を参照するには、コールバック内でしか有効とならず、（今回は1つだが）必要な処理が増えるほどネストも増えコードの見通しが悪くなる。</p>
<p>これを解決するには、本文でも触れたasyncモジュールを使う。npm install async &minus;&minus;saveでモジュールをインストールした上で、コードは以下のようになる。</p>
<p></p><pre class="crayon-plain-tag">// npm install async --save
var async = require('async');

var mongoClient = require('mongodb').MongoClient;
var mongodb = 'mongodb://localhost:27017/test';

async.series([
    function (callback) { step1(function() { callback(); }); },
    function (callback) { step2(function() { callback(); }); }
], function (err, results) {
    process.exit(0);
});

function step1(callback) {
    console.log('json read start');
    mongoClient.connect(mongodb, function(err, db) {
        var http = require('http');

        http.get('http://blog.iwh12.jp/test/tags.json', (json) =&gt; {
            var body = '';
            json.setEncoding('utf8');

            json.on('data', (chunk) =&gt; {
                body += chunk;
            });

            json.on('end', () =&gt; {
                var d = JSON.parse(body); 
                db.collection('tags').insertMany(d).then(function(err, r) {
                    db.close();
                    console.log('json read end');
                    callback();
                });
            });
        });
    });
}

function step2(callback) {
    console.log('search start');
    mongoClient.connect(mongodb, function(err, db) {
        db.collection('tags').find({}).toArray(function (err, tags) {
            console.log(tags);
            db.close();
            console.log('search end');
            callback();
        }); 
    });
}</pre><p>MongoDBへの読み込み（step1）と検索部分（step2）をどちらも関数にし、async.seriesへこのように書く。また各関数に引数callbackと、実際の処理終了部分にcallback();を追加。これにより、callback()が呼ばれるまで先に処理が進まず、見かけ上、同期作動となる。</p>
<p>全ての処理が終わるとfunction (err, results)の部分へ移り、（今回は）プログラムを終了。これなら非同期の数が増えてもコードの見通しが良くなるのがお分かり頂けだろうか。もちろん実行結果は二番目のコードと同じだ。</p>
<p>例えば、WordPress REST APIを使って記事を表示するには、該当するpost idを見つけ、その中にあるcategory idとtag idからカテゴリ名とタグ名を探し、前後の記事idを得るには、少なくともネストが4重になり、いろいろ処理を加えて行くとコードの見通しが非常に悪くなる。このような時に、asyncモジュールを使うと便利だ。</p>
<p>…と、言った内容を、PC Watchのあの後に追加するのは場違いのような気がしたので省略した次第。これで本当の（完）</p>
<p>余談になるが、macOSで構築している開発環境をBashへ移そうとしたところ、mount -t cifsやsshfsなどに対応しておらず、NASの共有フォルダがマウントできないことに気が付いた。RS2でやっとpingなどに対応したばかりなので、ある意味仕方ない部分だ。</p>
<p>ただ仕事場では案件ごとにフォルダ分けしてNASへ入れ、それをmacOSでmount、親フォルダをapacheのDocumentRootにしているが（Web系の内容が多いため）、Bashでは不可能。これでは開発環境を移行できない。</p>
<p>さらにPHPが7系。メンテナンスしている古いサイトはPHP 5.1とか普通にあり（笑）まずそのままでは動かない。このため<a href="https://blog.iwh12.jp/2017/02/02/%e3%81%93%e3%81%ae%e3%82%b5%e3%82%a4%e3%83%88%e3%81%aephp%e3%82%927-1-1%e3%81%b8/">バージョン違いのPHPをCGIで動かす</a>など、ちょっとした細工も必要となる。ここまでするならVMでLinuxを動かしても然程労力は変わらない感じがしないでもない。</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>WordPress REST API、Node.js + Express + EJS、MongoDBを使い複数サイトを一本化するPart1.5</title>
		<link>https://blog.iwh12.jp/2017/04/04/wordpress-rest-api%e3%80%81node-js-express-ejs%e3%80%81mongodb%e3%82%92%e4%bd%bf%e3%81%84%e8%a4%87%e6%95%b0%e3%82%b5%e3%82%a4%e3%83%88%e3%82%92%e4%b8%80%e6%9c%ac%e5%8c%96%e3%81%99%e3%82%8bpart1-5/</link>
		
		<dc:creator><![CDATA[knishika]]></dc:creator>
		<pubDate>Tue, 04 Apr 2017 01:31:04 +0000</pubDate>
				<category><![CDATA[日記]]></category>
		<category><![CDATA[Program]]></category>
		<guid isPermaLink="false">http://blog.iwh12.jp/?p=1218</guid>

					<description><![CDATA[Part1では、”APIを使って複数サイトのデータをMongoDBへ保存する方法”を説明したものの、少し腑に落ちないところがあったので該当部分を書き直した。従ってPart1.5となる（笑）そのままコピペすれば、作動するコ]]></description>
										<content:encoded><![CDATA[<p><img src="https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/dbreload.jpg" alt="WordPress REST API、Node.js + Express + EJS、MongoDBを使い複数サイトを一本化するPart1.5" width="100%" class="alignnone size-full wp-image-1219" srcset="https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/dbreload.jpg 1024w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/dbreload-300x216.jpg 300w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/dbreload-768x552.jpg 768w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/dbreload-30x22.jpg 30w" sizes="(max-width: 1024px) 100vw, 1024px" /><br />
<a href="https://blog.iwh12.jp/2017/04/03/wordpress-rest-api%e3%80%81node-js-express-ejs%e3%80%81mongodb%e3%82%92%e4%bd%bf%e3%81%84%e8%a4%87%e6%95%b0%e3%82%b5%e3%82%a4%e3%83%88%e3%82%92%e4%b8%80%e6%9c%ac%e5%8c%96%e3%81%99%e3%82%8bpart1/">Part1</a>では、”APIを使って複数サイトのデータをMongoDBへ保存する方法”を説明したものの、少し腑に落ちないところがあったので該当部分を書き直した。従ってPart1.5となる（笑）そのままコピペすれば、作動するコードも掲載するので参考まで。<span id="more-1218"></span></p>
<p>Part1で使用したasyncモジュールは、いろいろな作動モードがあり、async.seriesで動かした。この場合、callbackが呼ばれるまでは次の処理に進まず、見かけ上、同期作動となる。</p>
<p>ただ考えてみれば、collectionのdrop、各APIの読み込みは非同期で作動しても問題無く、順番待ちをしている時間が無駄になる。従って流れとしては…。</p>
<ol style="padding-left: 1em;">
<li>同期用のwp0にあるposts、categories、tagsのdropは非同期だが、全部終わるのを待つ</li>
<li>各APIの読み込みは非同期だが、全部終わるのを待つ</li>
<li>表示用のwpにあるposts、categories、tagsのdropは非同期だが、全部終わるのを待つ</li>
<li>最後にwp0をwpへコピーする</li>
</ol>
<p>以上の順番さえ守れば、DBの更新に影響なく、加えて、各パートは非同期なので、全てをasync.seriesで行うよりも処理時間が短くなる。このようなケースにはasync.parallelを使用する。</p><pre class="crayon-plain-tag">var mongoClient = require('mongodb').MongoClient;
var mongodb_org = 'mongodb://localhost:27017/wp0';
var mongodb_url = 'mongodb://localhost:27017/wp';

var assert = require('assert');
var async = require('async');

var POSTS = 'posts';
var CATEGORIES = 'categories';
var TAGS = 'tags';

var base_url = ['http://blog.iwh12.jp','http://techtalk.pcmatic.jp'];

dbReload();

function dbReload() {
    // drop all collection from wp0
    mongoClient.connect(mongodb_org, function(err, db) {
        assert.equal(null, err);
        async.parallel([
            function (callback) { db.collection(POSTS).drop(function(err) { callback(); }); },
            function (callback) { db.collection(TAGS).drop(function(err) { callback(); }); },
            function (callback) { db.collection(CATEGORIES).drop(function(err) { callback(); }); }
        ], function (err, results) {
            if (err) {
                throw err;
            }
            db.close();
            console.log('drop all collection from wp0.');
            step1();
        });
    });
}</pre><p>上のvar群は関連するモジュールや変数の宣言で特に説明の必要は無いだろう。ポイントは、async.parallelの部分。wp0の各collectionのdropは非同期で作動し、全部終了した時に、function (err, results)を実行する。</p><pre class="crayon-plain-tag">// get all API
    function step1() {
        var api = { tags:'/wp-json/wp/v2/tags', categories:'/wp-json/wp/v2/categories', posts:'/wp-json/wp/v2/posts' };

        async.parallel([
            //site 1
            function (callback) { SetCollection(TAGS, base_url[0], api.tags, function() { callback(); }); },
            function (callback) { SetCollection(CATEGORIES, base_url[0], api.categories, function() { callback(); }); },
            function (callback) { SetCollection(POSTS, base_url[0], api.posts, function() { callback(); }); },
            //site 2
            function (callback) { SetCollection(CATEGORIES, base_url[1], api.categories, function() { callback(); }); },
            function (callback) { SetCollection(POSTS, base_url[1], api.posts, function() { callback(); }); }
        ], function (err, results) {
            if (err) {
                throw err;
            }
            console.log('get all API done.');
            step2();
        });
    }</pre><p>step1()は、各APIで得たデータをmongoDBに保存する部分だ。前回は順番に行っていたが、今回は5つのAPIの読み込みが非同期作動となる。全て終了後、step2()を実行。</p><pre class="crayon-plain-tag">// drop all collection from wp
function step2() {
    mongoClient.connect(mongodb_url, function(err, db) {
        assert.equal(null, err);
        async.parallel([
            function (callback) { db.collection(POSTS).drop(function(err) { callback(); }); },
            function (callback) { db.collection(TAGS).drop(function(err) { callback(); }); },
            function (callback) { db.collection(CATEGORIES).drop(function(err) { callback(); }); }
        ], function (err, results) {
            if (err) {
                throw err;
            }
            db.close();
            console.log('drop all collection from wp.');
            step3();
        });
    });
}</pre><p>step2()は、wp0をwpへコピーする時、コピー先にcollectionがあると失敗するので、先に3つのcollectionをdropする。dbReload()直後と全く同じで対象がwp0かwpかの違いだけだ。</p><pre class="crayon-plain-tag">// db.copyDatabase("wp0","wp")
function step3() {
    mongoClient.connect(mongodb_org, function(err, db) {
        if (err) {
            console.log(err);
        } else {
            var mongoCommand = { copydb: 1, fromhost: "localhost", fromdb: "wp0", todb: "wp" };
            var admin = db.admin();
            admin.command(mongoCommand, function(commandErr, data) {
                if (!commandErr) {
                    console.log(data);
                    console.log('DB Reloaded.');
                } else {
                    console.log(commandErr.errmsg);
                    console.log('DB Reloaded error!');
                }
                db.close();
            });
        }
    });
}</pre><p>step3()はwp0からwpへDBをまるまるコピーする部分となる。これで一連の流れは完了する。</p>
<p>最後に、APIから得たデータをmongoDBへ保存する＝SetCollection()を掲載する。前回との違いは、フラグを使い中でcollectionをdropする部分を削除している（APIがhttpかhttpsかの判断も追加）。</p>
<p>と言うのも、非同期で作動する場合、記述した順に実行されるとは限らず、dropせずに追記してしまうケースが発生するからだ。今回はこれを避けるため、一番初めにwp0からdropしているのはご覧の通り。</p><pre class="crayon-plain-tag">// get data from API and set collection
function SetCollection(collection, base, url, callback) {
    var api_url = base + url + '?per_page=' + 10;
    var max = 0;
    var db;
    console.log('API url: ' + api_url);

    // connect to monogo server
    mongoClient.connect(mongodb_org, function(err, mongodb) {
        assert.equal(null, err);
        db = mongodb;
    });

    // get from API
    getFromAPI(0);

    // get JSON with paginate
    function getFromAPI(n) {
        if (api_url.slice(0,5) == 'https')
            var http = require('https');
        else
            var http = require('http');

        http.get(api_url + '&amp;page=' + (n + 1), (json) =&gt; {
            var body = '';

            json.setEncoding('utf8');

            json.on('data', (chunk) =&gt; {
                body += chunk;
            });

            json.on('end', () =&gt; {
                if (max == 0)
                    max = json.rawHeaders[parseInt(json.rawHeaders.indexOf('X-WP-TotalPages')) + 1];
                var d = JSON.parse(body); 
                db.collection(collection).insertMany(d).then(function(err, r) {
                    console.log(collection, 'page: ' + (n + 1) + ' (' + base +')');
                    if (max == n + 1) {
                        db.close();
                        callback();
                        return;
                    }
                    getFromAPI(n + 1);
                });
            });

        });
    }
}</pre><p>以前掲載した”<a href="https://blog.iwh12.jp/2017/03/03/wordpress-rest-api%e3%81%a7%e5%be%97%e3%81%9fjson%e3%82%92node-js%e3%81%a8mongodb%e3%82%92%e4%bd%bf%e3%81%84%e4%bf%9d%e5%ad%98%e3%81%99%e3%82%8b/">WordPress REST APIで得たJSONをNode.jsとMongoDBを使い保存する</a>”に関しても、今回のコードの方がよりロジカルなので、こちらを参考にして欲しい。単一サイトの場合は、step1()のsite 2が不要となる。</p>
<p>Part2で説明するExpress + EJSはmongoDBにwpさえあれば作動するので、今回の部分は別扱いにして、cronを使い一定間隔でDBへリロードするのがいいかも知れない。</p>
<p>実験サイトは<a href="http://collabo.iwh12.jp" target="_blank" class="external ext_icon">こちら</a>。</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>WordPress REST API、Node.js + Express + EJS、MongoDBを使い複数サイトを一本化するPart1</title>
		<link>https://blog.iwh12.jp/2017/04/03/wordpress-rest-api%e3%80%81node-js-express-ejs%e3%80%81mongodb%e3%82%92%e4%bd%bf%e3%81%84%e8%a4%87%e6%95%b0%e3%82%b5%e3%82%a4%e3%83%88%e3%82%92%e4%b8%80%e6%9c%ac%e5%8c%96%e3%81%99%e3%82%8bpart1/</link>
		
		<dc:creator><![CDATA[knishika]]></dc:creator>
		<pubDate>Mon, 03 Apr 2017 06:07:17 +0000</pubDate>
				<category><![CDATA[日記]]></category>
		<category><![CDATA[Program]]></category>
		<guid isPermaLink="false">http://blog.iwh12.jp/?p=1206</guid>

					<description><![CDATA[少し前に「Node.js+Express+EJSとWordPress REST APIでViewを再構築する実験」と「WordPress REST APIで得たJSONをNode.jsとMongoDBを使い保存する」を掲]]></description>
										<content:encoded><![CDATA[<p><img src="https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/multiwp1.jpg" alt="Wordpress REST API、Node.js + Express + EJS、MongoDBを使い複数サイトを一本化する" width="100%" class="alignnone size-full wp-image-1207" srcset="https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/multiwp1.jpg 1024w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/multiwp1-300x184.jpg 300w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/multiwp1-768x471.jpg 768w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/04/multiwp1-30x18.jpg 30w" sizes="(max-width: 1024px) 100vw, 1024px" /><br />
少し前に「<a href="https://blog.iwh12.jp/2017/02/26/node-jsexpressejs%e3%81%a8wordpress-rest-api%e3%81%a7view%e3%82%92%e5%86%8d%e6%a7%8b%e7%af%89%e3%81%99%e3%82%8b%e5%ae%9f%e9%a8%93/">Node.js+Express+EJSとWordPress REST APIでViewを再構築する実験</a>」と「<a href="https://blog.iwh12.jp/2017/03/03/wordpress-rest-api%e3%81%a7%e5%be%97%e3%81%9fjson%e3%82%92node-js%e3%81%a8mongodb%e3%82%92%e4%bd%bf%e3%81%84%e4%bf%9d%e5%ad%98%e3%81%99%e3%82%8b/">WordPress REST APIで得たJSONをNode.jsとMongoDBを使い保存する</a>」を掲載したので、今回はこれらの複合技。昨今、キュレーションサイトが問題になっているが、それどころではなく、複数のWordPressサイトをまるまるパクリ（笑）、一本化するサイトの構築方法となる。<span id="more-1206"></span></p>
<p>この時肝になるのが、”APIを使って複数サイトのデータをMongoDBへ保存する方法”と、”post、categories、tagsのIDが被る可能性があるので、それを回避する方法”だ。まずPart1では前者から説明したい。</p>
<p>サイトのソースは、自分のブログと、許可を得て、<a href="http://techtalk.pcmatic.jp" target="_blank" class="external ext_icon">http://techtalk.pcmatic.jp</a>のブログ、この2つを使っているが（画面キャプチャ上のタイトルがtechtalk.pcmatic.jp、下のタイトルがblog.iwh12.jp）、ロジック上はいくつでも対応可能となっている。</p>
<p>単一サイトからAPIを使いMongoDBにpost、categories、tagsのcollectionを保存するのはもう説明済だが、複数サイトを対象とした非同期での書き込みは、全て完全に終了したのを知るにはいろいろ面倒なことになる。</p>
<p>そこで今回はasync（npm install async）モジュールを使うことにした。使い方は以下の通り。</p>
<p></p><pre class="crayon-plain-tag">var async = require('async');
async.series([
    //site 1
    function (callback) {
        SetCollection(TAGS, base_url[0], '/wp-json/wp/v2/tags', true, function() { callback(null,null); });
    },
    function (callback) {
        SetCollection(CATEGORIES, base_url[0], '/wp-json/wp/v2/categories', true, function() { callback(null,null); });
    },
    function (callback) {
        SetCollection(POSTS, base_url[0], '/wp-json/wp/v2/posts', true, function() { callback(null,null); });
    },

    //site 2（tagsは無い）
    function (callback) {
        SetCollection(CATEGORIES, base_url[1], '/wp-json/wp/v2/categories', false, function() { callback(null,null); });
    },
    function (callback) {
        SetCollection(POSTS, base_url[1], '/wp-json/wp/v2/posts', false, function() { callback(null,null); });
    }

], function (err, results) {
    if (err) {
        throw err;
    }
    console.log('Series all done.');

    // response send
    res.send('DB Reloaded.');
});</pre><p>上から順にAPIが実行され、callbackが呼ばれるまでは次の処理に進まず、見かけ上、同期で作動する。</p>
<p>SetCollectionの4番目の引数はcollectionをdropするかどうか。以前説明したように、DBは差分ではなく、全件保存しなおしているため、2つめ以降のサイトはdropしないようにする。</p>
<p>そしてAPIを処理する側は、http.getのend部分を一部書き換える。追加したのはcallback()。これで一つのAPI呼出しとDBへの保存が終わったことを先のcallbackで知らせ、次の処理へ進むことが出来る。</p>
<p></p><pre class="crayon-plain-tag">json.on('end', () =&gt; {
        if (max == 0)
            max = json.rawHeaders[parseInt(json.rawHeaders.indexOf('X-WP-TotalPages')) + 1];
        var d = JSON.parse(body); 
        db.collection(collection).insertMany(d).then(function(err, r) {
            if (max == n + 1) {
                db.close();
                callback(); // add
                return;
            }
            getFromAPI(n + 1);
        });
    });</pre><p>以上で理論上はOKなのだが、実際はAPIを使ったデータの取得が（場合によっては）分単位になるため、DB書き換え中にサイトをアクセスすると、対象となるデータが不完全でエラーとなる。</p>
<p>これを回避するには、同じ構造の同期用DBと表示用DB、2つ持ち、APIでの同期が済み次第、表示用DBへ同期用DBをまるごとコピーするのが手っ取り早い。コードは以下の通り。</p>
<p></p><pre class="crayon-plain-tag">// db.copyDatabase("wp0","wp")
mongoClient.connect(mongodb_org, function(err, db) {
    if (err) {
        console.log(err);
    } else {
        var mongoCommand = { copydb: 1, fromhost: "localhost", fromdb: "wp0", todb: "wp" };
        var admin = db.admin();
        admin.command(mongoCommand, function(commandErr, data) {
            if (!commandErr) {
                console.log(data);
            } else {
                console.log(commandErr.errmsg);
            }
            db.close();
        });
    }
});</pre><p>これはmongoコンソールで、db.copyDatabase(&#8220;wp0&#8243;,&#8221;wp&#8221;)とするのと同じ動きとなる。先のAPIでの読み込みをwp0へ行った後、console.log(&#8216;Series all done.&#8217;);の後ろにあるこのプログラムでwpへまるまるコピーする。これなら瞬時なので、表示側に影響が出ることは（完全に無いとは言い切れないものの）無いだろう。</p>
<p>但し、表示側（ここではwp）のcollection 3つを先にdropしてからコピーする必要がある。</p>
<p>作動中のサイトは<a href="http://collabo.iwh12.jp" target="_blank" class="external ext_icon">こちら</a>。サイドバーのウィジェットは全てExpressでルーティングしたAPIを使って描画している（Part2で説明予定）。</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>WordPress REST APIで得たJSONをNode.jsとMongoDBを使い保存する</title>
		<link>https://blog.iwh12.jp/2017/03/03/wordpress-rest-api%e3%81%a7%e5%be%97%e3%81%9fjson%e3%82%92node-js%e3%81%a8mongodb%e3%82%92%e4%bd%bf%e3%81%84%e4%bf%9d%e5%ad%98%e3%81%99%e3%82%8b/</link>
		
		<dc:creator><![CDATA[knishika]]></dc:creator>
		<pubDate>Fri, 03 Mar 2017 00:16:05 +0000</pubDate>
				<category><![CDATA[日記]]></category>
		<category><![CDATA[Program]]></category>
		<guid isPermaLink="false">http://blog.iwh12.jp/?p=790</guid>

					<description><![CDATA[Node.js+Express+EJSを使い遊んだところで、次はMongoDBとなるのは人情（笑）タイトルにあるように、WordPress REST APIで得たJSONをMongoDBへ保存したい。これができれば、Ex]]></description>
										<content:encoded><![CDATA[<p><img src="https://blog.iwh12.jp/wp/wp-content/uploads/2017/03/mongoDB.jpg" alt="mongoDB" width="100%" class="alignnone size-full wp-image-791" srcset="https://blog.iwh12.jp/wp/wp-content/uploads/2017/03/mongoDB.jpg 1024w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/03/mongoDB-300x216.jpg 300w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/03/mongoDB-768x552.jpg 768w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/03/mongoDB-30x22.jpg 30w" sizes="(max-width: 1024px) 100vw, 1024px" /><br />
<a href="https://blog.iwh12.jp/2017/02/26/node-jsexpressejs%e3%81%a8wordpress-rest-api%e3%81%a7view%e3%82%92%e5%86%8d%e6%a7%8b%e7%af%89%e3%81%99%e3%82%8b%e5%ae%9f%e9%a8%93/">Node.js+Express+EJSを使い遊んだところで</a>、次はMongoDBとなるのは人情（笑）タイトルにあるように、WordPress REST APIで得たJSONをMongoDBへ保存したい。これができれば、Expressで最小限の軽いAPIを作りクライアントとやり取りしたり、Node.jsで書いたViewに組み込みEJSへ渡すことも簡単!<br />
<span id="more-790"></span></p>
<p><a href="https://www.mongodb.com/" target="_blank" class="external ext_icon">MongoDB</a>はNoSQLと呼ばれるDBの一種。JSONフォーマットのまま、keyとvalueを操作でき、SQLでdb、table、row、columnに該当する部分が順に、db、collection、document、fieldとなるイメージだ。</p>
<p>今回、WordPress REST APIで得たJSONをMongoDBで扱うのに、db=wp、collection=tags、categories、postsとした。Node.jsとMongoDBのインストールは省略。既に作動するものとして話を進めたい。macOSでもWindowsのBashでも簡単に環境を構築できる。</p>
<p>使用するモジュールは以下の通り。事前にnpm install xxxx &minus;&minus;saveで用意する。</p>
<ul style="padding-left: 1em">
<li><a href="https://github.com/mongodb/node-mongodb-native" target="_blank" class="external ext_icon">mongodb</a></li>
<li><a href="https://github.com/defunctzombie/commonjs-assert" target="_blank" class="external ext_icon">assert</a></li>
<li>http</li>
</ul>
<p>早速コードその1。ここはロジックに必要な変数などと、MongoDBの初期化まで（}はまだ閉じてないので要注意。その1と2をコピペすれば作動するが、タイムアウトなどのエラー処理は含まれていない）。</p>
<p></p><pre class="crayon-plain-tag">var mongoClient = require('mongodb').MongoClient;
var mongodb_url = 'mongodb://localhost:27017/wp';
var assert = require('assert');

var http = require('http');
var base_url = 'http://blog.iwh12.jp';
var per_page = 10;

var POSTS = 'posts';
var CATEGORIES = 'categories';
var TAGS = 'tags';

setCollection(TAGS, '/wp-json/wp/v2/tags', false);
setCollection(CATEGORIES, '/wp-json/wp/v2/categories', false);
setCollection(POSTS, '/wp-json/wp/v2/posts', true);

// get data from API and set collection
function setCollection(collection, url, exit) {
    var api_url = base_url + url + '?per_page=' + per_page;
    var max = 0;
    var total = 0;
    var count = 0;
    var db;

    // connect to mongo server
    mongoClient.connect(mongodb_url, function(err, mongodb) {
        assert.equal(null, err);
        console.log(&quot;Connected to server: wp.&quot; + collection);
        db = mongodb;
        db.collection(collection).drop(function(err) {
            console.log('Drop collection: ' + collection);
        });
    });</pre><p></p>
<p>ここでの肝は、はじめにcollection=tags、categories、postsをdropしている点となる(有無を確認せずいきなりdropでも動く)。これを行わないとプログラムを実行するたびに（ロジックにもよるが）各collectionに同じものも含め追加されてしまうからだ。</p>
<p>もともとは全件読み込んだ後にこのプログラムを動かすと追加分だけマージする予定だったが、考えてみればAPIから簡単に得られるのは全件数のみ。</p>
<p>DBの件数と比較してもし+αになっていても、純粋に+α分追加したのか、1件削除+α+1件でその値になったのか知る由もない。また古い内容も何かのタイミングで再編集している可能性がある。</p>
<p>そう考えると、どのみちAPIを使うとデータが飛んできてしまうので、小細工は不要。全件リロードするため、事前にcollectionをdropしている（もちろん全件削除でもいい）。また、dbとcollectionは、無いと自動的に作られる。</p>
<p>コードその2は、各collectionのJSONを得て、MongoDBへ保存している部分。全て再帰呼出しなので件数に制限は無い。exitは、全件取得後、プログラムを終了するか、継続するかのフラグとなる。</p>
<p></p><pre class="crayon-plain-tag">// get from API
    getFromAPI(0);

    // get JSON with paginate
    function getFromAPI(n) {
        http.get(api_url + '&amp;page=' + (n + 1), (json) =&gt; {
            var body = '';

            json.setEncoding('utf8');

            json.on('data', (chunk) =&gt; {
                body += chunk;
            });

            json.on('end', () =&gt; {
                if (max == 0) {
                    max = json.rawHeaders[parseInt(json.rawHeaders.indexOf('X-WP-TotalPages')) + 1];
                    total = json.rawHeaders[parseInt(json.rawHeaders.indexOf('X-WP-Total')) + 1];
                }
                var d = JSON.parse(body); 
                d.forEach(function(elem, index) {
                    db.collection(collection).insertOne(elem).then(function(err, r) {
                        console.log(collection, 'page: ' + (n + 1),'item: ' + (index + 1));
                        count++;
                        if (exit &amp;&amp; total == count) {
                            db.close();
                            process.exit(0);
                        }
                    });
                })
                if (max == n + 1)
                    return;
                getFromAPI(n + 1);
            });

        });
    }
}</pre><p></p>
<p>ここは前回の「<a href="https://blog.iwh12.jp/2017/02/28/wordpress-4-7%e3%81%aerest-api%e3%82%92%e4%bd%bf%e3%81%a3%e3%81%9f%e8%a8%98%e4%ba%8b%e9%96%b2%e8%a6%a7%e3%83%97%e3%83%ad%e3%82%b0%e3%83%a9%e3%83%a0%e3%80%81post%e6%95%b0%e7%84%a1%e5%88%b6%e9%99%90/">WordPress 4.7のREST APIを使った記事閲覧プログラム、Post数無制限版</a>」と内容がほぼ同じ。forEachでdocument毎にinsertOne()している。ただMongoDBへの書き込みも非同期でちょっと工夫したのがexitの部分だ。</p>
<p>と言うのも、何も考えずにコーディングすると、if (max == n + 1) return;のところへ、exit==trueならprocess.exit(0);と書きそうになる。が、このタイミングでは、まだMongoDBへの書き込みが終わっておらず実際の件数と合わなくなるケースが発生する。</p>
<p>確実なのはCallback内で、total（=X-WP-Total）と書き込み回数（=count）が同じになったタイミングだ。</p>
<p>実はnの要素を持つJSONをまとめてInsertできる<a href="https://docs.mongodb.com/v3.2/reference/method/db.collection.insertMany/" target="_blank" class="external ext_icon">insertMany()</a>というmethodもあり、forEach無しでper_page分、いきなり全部保存も可能。こちらの方がコードもスッキリするし、おそらくMongoDBの作動も速い。</p>
<p></p><pre class="crayon-plain-tag">// forEach、total、countが不要
var d = JSON.parse(body); 
db.collection(collection).insertMany(d).then(function(err, r) {
    console.log(collection, 'page: ' + (n + 1));
    if (exit &amp;&amp; n + 1 == max) {
        db.close();
        process.exit(0);
    }
});</pre><p></p>
<p>これで無事JSON全件が保存された。確認はこんな感じでできる。</p>
<p>$ mongo<br />
&gt; use wp<br />
switched to db wp<br />
&gt; show collections<br />
categories<br />
posts<br />
tags<br />
&gt; db.posts.count();<br />
39<br />
&gt; db.posts.find({id:769}, {title:1,_id:0});<br />
{ &#8220;title&#8221; : { &#8220;rendered&#8221; : &#8220;WordPress 4.7のREST APIを使った記事閲覧プログラム、Post数無制限版&#8221; } }<br />
※<sup>1</sup> :1は表示、:0は非表示（_id=primary keyは必ず表示される）<br />
※<sup>2</sup> 同様にcollectionのtags、categoriesも操作できる。dbの削除はdb.dropDatabase();</p>
<p>日頃MySQLにお世話になってる身としては、何といっても感動は、詳細を知らないJSONをinsertOne()やinsertMany()でドン！と入れれることと、JSONのまま検索などができること。この手のシステムに向いているDBと言える。</p>
<p>後は、冒頭に書いたようにExpressで簡単なAPIのルーティングを書き、クライアントとやり取りするか、Express＋EJSでViewを書くなどいろいろパターンが考えられる。ただ、連日遊び過ぎでちょっと燃え尽きたので続きが何時になるかは未定（笑）</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>WordPress 4.7のREST APIを使った記事閲覧プログラム、Post数無制限版</title>
		<link>https://blog.iwh12.jp/2017/02/28/wordpress-4-7%e3%81%aerest-api%e3%82%92%e4%bd%bf%e3%81%a3%e3%81%9f%e8%a8%98%e4%ba%8b%e9%96%b2%e8%a6%a7%e3%83%97%e3%83%ad%e3%82%b0%e3%83%a9%e3%83%a0%e3%80%81post%e6%95%b0%e7%84%a1%e5%88%b6%e9%99%90/</link>
		
		<dc:creator><![CDATA[knishika]]></dc:creator>
		<pubDate>Tue, 28 Feb 2017 12:44:37 +0000</pubDate>
				<category><![CDATA[日記]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Program]]></category>
		<guid isPermaLink="false">http://blog.iwh12.jp/?p=769</guid>

					<description><![CDATA[少し前に掲載した「WordPress 4.7のREST APIを使った記事閲覧プログラム」は、簡易版ということもあり、記事数は最大100件まで。これはREST APIの最大100件/回の制限からきたものだ。このBLOGだ]]></description>
										<content:encoded><![CDATA[<p><img src="https://blog.iwh12.jp/wp/wp-content/uploads/2017/02/wp_post_list_unlimited_ver.jpg" alt="wp_post_list_unlimited" width="100%" class="alignnone size-full wp-image-782" srcset="https://blog.iwh12.jp/wp/wp-content/uploads/2017/02/wp_post_list_unlimited_ver.jpg 991w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/02/wp_post_list_unlimited_ver-300x238.jpg 300w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/02/wp_post_list_unlimited_ver-768x609.jpg 768w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/02/wp_post_list_unlimited_ver-30x24.jpg 30w" sizes="(max-width: 991px) 100vw, 991px" /><br />
少し前に掲載した「<a href="https://blog.iwh12.jp/2017/02/14/wordpress-4-7%e3%81%aerest-api%e3%82%92%e4%bd%bf%e3%81%a3%e3%81%9f%e8%a8%98%e4%ba%8b%e9%96%b2%e8%a6%a7/">WordPress 4.7のREST APIを使った記事閲覧プログラム</a>」は、簡易版ということもあり、記事数は最大100件まで。これはREST APIの最大100件/回の制限からきたものだ。このBLOGだとまだ40件にも満たないので100件超えは数カ月後になるが、記事数無制限版に改良した。<span id="more-769"></span></p>
<p>ポイントはper_pageパラメータnの時に、何ページ分の記事があるかを知る方法と（＝ajaxを実行する回数）、非同期でajaxを使う場合、Callbackの中でしか値を参照できず、ページ分ループし得た値をマージするにはちょっとしたコツが必要となる（同期でajaxを使う場合は普通のループで問題無い）。</p>
<p>主要部分のコードは以下の通り。キーは、レスポンスヘッダにあるX-WP-TotalPagesと、ファンクションの再帰呼出し（recursive call）。</p>
<p></p><pre class="crayon-plain-tag">var posts_per_page = 10;
var url_base = 'http://blog.iwh12.jp/wp-json/wp/v2';
var url_posts = url_base + '/posts?per_page=' + posts_per_page;

// get Posts
function getPosts() {
    var posts_all = [];
    var max = 0;

    getPages(0);

    // get n page until max
    function getPages(n) {
        $.ajax({
            type: 'get',
            url: url_posts + '&amp;page=' + (n + 1),
            dataType: 'json'
        })
        .done(function(data, textStatus, jqXHR) {
            Array.prototype.push.apply(posts_all, data);
            if (max == 0)
                max = parseInt(jqXHR.getResponseHeader('X-WP-TotalPages'));
            if (n + 1 == max)
                setDataTables(posts_all);
            else
                getPages(n + 1);
        });
    }
}</pre><p> </p>
<p>コードから分かるように、全てのデータをマージするため、Callback内でgetPages()をページ数だけ再帰呼出ししている。また初めの一回目にレスポンスヘッダから最大ページ数を保存、その値と、カレントのページ数を比較して、同じになるまで配列のマージを繰り返し、同じになったらDataTablesに値をセットして戻る…といった感じだ。これで記事数が100件超えてもいくらでも読み込める（最大何件まで行けるかは不明）。</p>
<p>ただ以前のコードは、そもそもWPがJSONを組み立てるのが遅く（ショートコードやfunctions.phpも展開するため）、それを解決するのにcrontabへcurlを仕込み、ファイルでのやり取りをしていたが、この手が使えなくなってしまった。最初に全件ロードするのでそれなりに時間がかかるものの、一旦ロードすれば、後はオンメモリなので、動作は非常に速く快適に操作可能だ。</p>
<p>こうしてpostに関しては無制限になったが、category（子category未対応）とtagは最大100件のまま。これに引っかかることはあまり無いと思われるので省略した（同じ方法で対応は可能）。</p>
<p>改良版の記事閲覧プログラムは<a href="https://blog.iwh12.jp/test/wp_posts_list_unlimited.html" target="_blank">こちら</a>。</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Node.js+Express+EJSとWordPress REST APIでViewを再構築する実験</title>
		<link>https://blog.iwh12.jp/2017/02/26/node-jsexpressejs%e3%81%a8wordpress-rest-api%e3%81%a7view%e3%82%92%e5%86%8d%e6%a7%8b%e7%af%89%e3%81%99%e3%82%8b%e5%ae%9f%e9%a8%93/</link>
		
		<dc:creator><![CDATA[knishika]]></dc:creator>
		<pubDate>Sun, 26 Feb 2017 00:22:12 +0000</pubDate>
				<category><![CDATA[日記]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Program]]></category>
		<guid isPermaLink="false">http://blog.iwh12.jp/?p=728</guid>

					<description><![CDATA[少し前に「WordPress 4.7のREST APIを使った記事閲覧プログラム」を掲載したが、これはクライアント側（ブラウザ）のみでの実験。今度はサーバー側にNode.jsを使ってBLOGのViewを再構築することに…]]></description>
										<content:encoded><![CDATA[<p><img src="https://blog.iwh12.jp/wp/wp-content/uploads/2017/02/nodejs.jpg" alt="Node.js+Express+EJS" width="100%" class="alignnone size-full wp-image-729" srcset="https://blog.iwh12.jp/wp/wp-content/uploads/2017/02/nodejs.jpg 1024w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/02/nodejs-300x200.jpg 300w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/02/nodejs-768x511.jpg 768w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/02/nodejs-30x20.jpg 30w" sizes="(max-width: 1024px) 100vw, 1024px" /><br />
少し前に「<a href="https://blog.iwh12.jp/2017/02/14/wordpress-4-7%e3%81%aerest-api%e3%82%92%e4%bd%bf%e3%81%a3%e3%81%9f%e8%a8%98%e4%ba%8b%e9%96%b2%e8%a6%a7/">WordPress 4.7のREST APIを使った記事閲覧プログラム</a>」を掲載したが、これはクライアント側（ブラウザ）のみでの実験。今度はサーバー側にNode.jsを使ってBLOGのViewを再構築することに…。<span id="more-728"></span></p>
<p>これまでNode.jsはメッセージング系で少し触ったことがあるものの、Webサーバー、つまりApacheの替わりとしては使ったことがなかったので、WordPress 4.7のREST APIがある程度分かったついでに実験したくなった（笑）。</p>
<p>但し、管理画面も実装するならともかく、Viewだけだとクライアント側のみで出来るので、わざわざサーバー側でやることもなく、あくまでも遊びだ。</p>
<h3>全体の概要</h3>
<p>Node.jsの開発環境はググるといろいろ出てくるので省略。当初はWindowsのBashを使用したものの、WindowsからBashへのファイルのやり取りが面倒（逆はOK）だったので<sup>*追記あり</sup>、後半はmacOSへ移行した。どちらも<a href="https://github.com/hokaccha/nodebrew" target="_blank" class="external ext_icon">nodebrew</a>を使えば簡単に環境を構築できる。</p>
<p>npmでインストールしたモジュールは以下の通り。</p>
<ul style="padding-left: 1em;">
<li><a href="http://expressjs.com/ja/" target="_blank" class="external ext_icon">express</a></li>
<li><a href="https://github.com/mde/ejs" target="_blank" class="external ext_icon">ejs</a></li>
<li>http</li>
<li>compression</li>
<li>memory-cache</li>
<li>lru-cache</li>
</ul>
<p>最後の3つは軽くパフォーマンスアップするもので無くても作動する。またサイトがhttpsの場合は、http（http.get）モジュールではなくhttpsモジュール（https.get）を使用する。</p>
<p>ここにコードを載せても載りきらないので要点はこんな感じだ。</p>
<ul style="padding-left: 1em;">
<li>Expressでのルーティング<br />
app.get(&#8216;/&#8217; &#8230;<br />
app.get(&#8216;/page/:pages&#8217; &#8230;<br />
app.get(&#8216;/tag/:tags/page/:pages&#8217; &#8230;<br />
app.get(&#8216;/category/:categories/page/:pages&#8217; &#8230;<br />
app.get(&#8216;/post/:postId&#8217; &#8230;<br />
※例えば/pageといった、1ページだけのルーティングは省略<br />&nbsp;
</li>
<li>各ルーティングで必要なデータをREST APIで得る<br />&nbsp;</li>
<li>
一覧表示の各内容は、content.renderedにimgなどタグも含めそのまま入っているので、&lt;!&minus;&minus;more&minus;&minus;&gt;までで切り出す<br />&nbsp;
</li>
<li>
paginateで必要な最大件数/最大ページ数はJSONにはなく、レスポンスヘッダのX-WP-TotalとX-WP-TotalPagesに入っているので、例えばhttp.get(url, (d)とすると、d.rawHeaders[]のn番目に先の文字列があり、n+1が欲しい値となる<br />&nbsp;
</li>
<li>REST APIで得たデータを加工してテンプレートejsへ渡す<br />
index.ejs（他の一覧も兼ねる）<br />
post.ejs
</ul>
<p>テンプレートはこの2本だけだが、実際はヘッダー、サイドバー、フッターなどを別ejsにしてincludeするよくあるパターン。htmlのフレームワークはお馴染み<a href="http://getbootstrap.com/" target="_blank" class="external ext_icon">Bootstrap</a>だ。</p>
<h3>API仕様の気になる点</h3>
<p>プログラム自体は簡単だが、実際に動かして思ったのは、純粋に一覧だけ（title、date、categories、tags、featured_media程度のみ）得るAPIが無いため、何をするにしてもpostsでデータを得る必要がり、JSONのデータ量と、それを作るときのサーバー側の負荷（ショートコードやfunctions.phpの内容も展開する）が結構かかる。</p>
<p>加えてサムネイル画像を一覧に出そうとすると_embedオプションを付けなければならず、更にデータ量が増す。ここのように遅いサーバーだとその差は体感ではっきりわかるほどだ。</p>
<p>本家はたまたまアイキャッチ画像（featured_media）を使わず、記事の頭に大き目の画像を入れてあるので、これには引っかからなかったが、一般的には重い処理となるだろう。</p>
<p>本家にあって、こちらに無いのは「前後記事へのリンク」と「Archivesの年月一覧」。今の<a href="https://developer.wordpress.org/rest-api/reference/posts/" target="_blank" class="external ext_icon">API仕様</a>ではどうみても全件データを引っ張って解析しない限りこの2つには対応できないように思う。</p>
<h3>一部クライアント側でレンダリング</h3>
<p>一点手抜き（？）したのは、サイドバーの各一覧。これはサーバー側ではなく、クライアント側で書いている。もともとウィジェットを置く場所なのでこれもありかなと…。</p>
<p>ちょっとアクロバティックなのはPopular Posts。これは<a href="https://ja.wordpress.org/plugins/wordpress-popular-posts/" target="_blank" class="external ext_icon">WordPress Popular Posts</a>がウィジェットとして表示しているもので、実体はadmin-ajax.php?action=wpp_get_popularを呼ぶとリストのhtmlが戻ってくる形になっている。ただそのまま呼ぶとクロスドメイン制約で値を得られない。</p>
<p>そこで同じサーバー内にあるのを利用して、curlを使い一定間隔でそのhtmlをファイルへ保存、参照する形にしている。</p>
<p>これで無事表示は出来るのだがリンク先は本家。実験サイトではないため、このままだとあまり意味がない。また本家のURLはタイトルベース、実験サイトはIDベースなので、ドメインの部分だけ置換しても作動しない。</p>
<p>「うーん」と思っていたところ、表示するサムネイル画像のファイル名にIDが入っていることが分かり、それを使って実験サイトへのURLになるよう書き換えた（笑）</p>
<h3>雑感</h3>
<p>仕上げはサーバーへの設置だ。Proxyを設定するのが面倒なので、iptablesでポート3000を開けそのまま外に出している。この時、スクリプトをデーモン化する必要があり、<a href="https://github.com/foreverjs/forever" target="_blank" class="external ext_icon">forever</a> -w start app.jsで起動。無事、外からアクセスできるようになった。</p>
<p>一式やってみた感想は、当初Node.jsの話を聞いたとき、既にいろいろあるのに何故今更JSと思ったが、サーバー側もクライアント側も全てJSで書け分かり易い上にデバックもし易く、これはありだ。</p>
<p>またテンプレートエンジンのEJSは、htmlそのまま&lt;% %&gt;の中だけJSの世界となりとっつき易い。つまり普段、LAMPを使っていれば、ベースの部分は何も学ぶ必要がなく、各モジュールの仕様だけ理解すれば（しかもJS）即扱える。これはなかなか楽しいかも知れない。</p>
<p>Node.js+Express+EJSで再構築したBLOGのViewは<a href="https://blog.iwh12.jp:3000" target="_blank" class="external ext_icon">こちら</a>。</p>
<h3>追記</h3>
<p>「WindowsからBashへのファイルのやり取りが面倒」と書いたが、/mnt/c/Users/にWindowsのファイルシステムがマウントされているので、ここならどちらからでも普通にアクセスできる。</p>
<p>更にVisual Studio Codeの総合ターミナルをBashに変更すればかなりGoodな環境となる。</p>
<p><img src="https://blog.iwh12.jp/wp/wp-content/uploads/2017/02/vsc_on_bash.jpg" alt="Visual Studio Codeの統合ターミナル" width="100%" class="alignnone size-full wp-image-761" srcset="https://blog.iwh12.jp/wp/wp-content/uploads/2017/02/vsc_on_bash.jpg 1016w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/02/vsc_on_bash-300x233.jpg 300w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/02/vsc_on_bash-768x596.jpg 768w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/02/vsc_on_bash-30x23.jpg 30w" sizes="(max-width: 1016px) 100vw, 1016px" /><br />
Visual Studio Codeの総合ターミナルでBashが作動中</p>
<p>方法はメニューのファイル/設定/基本設定/ユーザー設定で、</p><pre class="crayon-plain-tag">{
&quot;terminal.integrated.shell.windows&quot;: &quot;C:\\Windows\\sysnative\\bash.exe&quot;,
&quot;termnial.integrated.shell.unixlike&quot;: &quot;bash.exe&quot;
}</pre><p>これを追加すればOK。もちろんviやnode app.jsもこの中でそのまま作動！</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>WordPressのAMPプラグインを使って特定の記事のみAMPを無効にする</title>
		<link>https://blog.iwh12.jp/2017/02/18/wordpress%e3%81%aeamp%e3%83%97%e3%83%a9%e3%82%b0%e3%82%a4%e3%83%b3%e3%82%92%e4%bd%bf%e3%81%a3%e3%81%a6%e7%89%b9%e5%ae%9a%e3%81%ae%e8%a8%98%e4%ba%8b%e3%81%ae%e3%81%bfamp%e3%82%92%e7%84%a1%e5%8a%b9/</link>
		
		<dc:creator><![CDATA[knishika]]></dc:creator>
		<pubDate>Sat, 18 Feb 2017 11:21:52 +0000</pubDate>
				<category><![CDATA[日記]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Program]]></category>
		<guid isPermaLink="false">http://blog.iwh12.jp/?p=622</guid>

					<description><![CDATA[Search Consoleを見るとAMP関連で何やらエラーが出ている。記事はこれ。単にInstagramを張り付けて少しコメントを入れただけのもの。エラー内容はImageObjectに値が無い…っと言われても。 Ins]]></description>
										<content:encoded><![CDATA[<p><article class="shortcode_share"><figure><a href="https://ikisakianco.com/wordpress-disable-amp-with-plugin" target="_blank" class="external"><img src="" alt="image" class="share_pict" border="0" width="100%" /></a></figure></article>Search Consoleを見るとAMP関連で何やらエラーが出ている。記事は<a href="https://blog.iwh12.jp/2017/01/12/遂に♪/">これ</a>。単にInstagramを張り付けて少しコメントを入れただけのもの。エラー内容はImageObjectに値が無い…っと言われても。<span id="more-622"></span></p>
<p>Instagramを張り付けた時、ImageObjectを付加しないよう、エラーを根本的に修正するにはAMPプラグインの内部を触らないとできないため＝無理（笑）。この記事だけAMPを除外する必要がある。いろいろググったところ発見！</p>
<p>記事編集画面で個別にAMPをON/OFFできるので非常に便利。コードをそのままfunctions.phpにコピペして、問題無く作動している。感謝♪</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>WordPress 4.7のREST APIを使った記事閲覧プログラム</title>
		<link>https://blog.iwh12.jp/2017/02/14/wordpress-4-7%e3%81%aerest-api%e3%82%92%e4%bd%bf%e3%81%a3%e3%81%9f%e8%a8%98%e4%ba%8b%e9%96%b2%e8%a6%a7/</link>
		
		<dc:creator><![CDATA[knishika]]></dc:creator>
		<pubDate>Tue, 14 Feb 2017 02:00:25 +0000</pubDate>
				<category><![CDATA[日記]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Program]]></category>
		<guid isPermaLink="false">http://blog.iwh12.jp/?p=517</guid>

					<description><![CDATA[昨日「もう少し本格的なのを作ってみるかな…。」と書いたので、少し実用的（？）なものを作ってみた。APIから得たJSONをJQuery DataTablesへセット、記事の一覧、検索、表示ができるように…。 一覧は画面キャ]]></description>
										<content:encoded><![CDATA[<p><img src="https://blog.iwh12.jp/wp/wp-content/uploads/2017/02/wp_post_list.jpg" alt="wp_post_list.html" width="100%" class="alignnone size-full wp-image-518" srcset="https://blog.iwh12.jp/wp/wp-content/uploads/2017/02/wp_post_list.jpg 1024w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/02/wp_post_list-300x172.jpg 300w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/02/wp_post_list-768x441.jpg 768w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/02/wp_post_list-30x17.jpg 30w" sizes="(max-width: 1024px) 100vw, 1024px" /><br />
昨日「もう少し本格的なのを作ってみるかな…。」と<a href="https://blog.iwh12.jp/2017/02/13/wordpress-4-7のrest-api/">書いた</a>ので、少し実用的（？）なものを作ってみた。APIから得たJSONをJQuery <a href="https://datatables.net/" target="_blank" class="external ext_icon">DataTables</a>へセット、記事の一覧、検索、表示ができるように…。<span id="more-517"></span></p>
<p>一覧は画面キャプチャから分かるように、Id|Date|Title|Category|Tagsが並び、選ぶと下にcssのcolumnsを使った三段組のViewが表示される。こうしたのは出来るだけスクロールして読みたくなかったから。妙な部分で切れるケースもあるが、実験的な意味合いなので気にしないことにする。</p>
<p>検索は、一覧の項目内容に加え、実際のContentの内容も対象になっている。これはDataTablesにデータを渡しているが、 { data: &#8220;content&#8221;, visible: false }と、非表示にしているので一覧では見えないだけだ。</p>
<p>ContentをDataTablesへセットする時の肝は、JSONで得られるContentはhtmlがそのまま入っているので、imgがあった場合（srcset=も含む）、テーブルロード時に、imgデータも一緒にGETしに行き、余計にロード時間がかかってしまう上、非表示なので何の役にもたたない。</p>
<p>escape／unescapeを自作するのは面倒なので、<a href="http://underscorejs.org/" target="_blank" class="external ext_icon">Underscore.js</a>を使った。これでテーブルロード時、記事中にあるimgをGETしなくなる。もちろんViewに書くときはunescapeしなければならない。</p>
<p>30件未満の記事をロードしてこの待ち時間。基本テキストデータと言うこともあり、1000件程度はオンメモリでも大丈夫だと思うが、待ち時間が長くなる。実用的にする場合はpaginateの実装が必要だろう。ただそうすると検索が一覧にある範囲しか効かなくなるので痛し痒し…。</p>
<p>とは言え、APIの仕様上、最大100件／回なので、いずれにしても件数が増えればpaginateするか、JSONを分割してそれをマージする必要がある。</p>
<p>しかしこれ、サーバー側で対策しておかないと、複数のWPサイトからJSONをGETしてマージすれば、簡単にキュレーション（と言うよりパクリ）サイトができてしまう。流石に企業は無いと思うが、個人レベルなら技術的にも簡単なので実在するかも！？</p>
<p>デモサイトは<a href="https://blog.iwh12.jp/test/wp_posts_list.html" target="_blank">こちら</a>。ついでに上のメニューにも追加（笑）</p>
<p>追記1：どうやらJSONのやり取り以前に、WordpressがJSONへ値を作り込む方が時間がかかっているので、crontabにcurlをセット、一定間隔でJSONファイルを作成、それを読み込むように変更した。</p>
<p>多少本家のBLOGとタイムラグが発生するものの、毎日何本も書かないので問題無い。大幅に速くなった♪（キャッシュ更新のタイミングでcurl動かす手もあるか…。）</p>
<p>追記2：<a href="http://getbootstrap.com" target="_blank" class="external ext_icon">Bootstrap</a>を使って書き換え、幅によって、Contentを3段2段1段表示へ</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>WordPress 4.7のREST API</title>
		<link>https://blog.iwh12.jp/2017/02/13/wordpress-4-7%e3%81%aerest-api/</link>
		
		<dc:creator><![CDATA[knishika]]></dc:creator>
		<pubDate>Mon, 13 Feb 2017 05:39:11 +0000</pubDate>
				<category><![CDATA[日記]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Program]]></category>
		<guid isPermaLink="false">http://blog.iwh12.jp/?p=503</guid>

					<description><![CDATA[最新の4.7.2では修正されているものの、今、世間を騒がしているREST APIの脆弱性を使ったサイトの改ざん。頻繁にアップデートしないサイトも多いので、総数で数百万単位のページが影響を受けたようだ。もちろん、このサイト]]></description>
										<content:encoded><![CDATA[<p><img src="https://blog.iwh12.jp/wp/wp-content/uploads/2017/02/wp47_rest_api.jpg" alt="Wordpress 4.7 REST API" width="100%" class="alignnone size-full wp-image-504" srcset="https://blog.iwh12.jp/wp/wp-content/uploads/2017/02/wp47_rest_api.jpg 1024w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/02/wp47_rest_api-300x153.jpg 300w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/02/wp47_rest_api-768x392.jpg 768w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/02/wp47_rest_api-30x15.jpg 30w" sizes="(max-width: 1024px) 100vw, 1024px" /><br />
最新の4.7.2では修正されているものの、<a href="http://www.itmedia.co.jp/enterprise/articles/1702/13/news045.html" target="_blank" class="external ext_icon">今、世間を騒がしているREST APIの脆弱性を使ったサイトの改ざん</a>。頻繁にアップデートしないサイトも多いので、総数で数百万単位のページが影響を受けたようだ。もちろん、このサイトはアップデート済だが、少し気になったので、REST APIを調べてみた。<span id="more-503"></span></p>
<p>REST APIの仕様は<a href="https://developer.wordpress.org/rest-api/reference/" target="_blank" class="external ext_icon">ここ</a>にあるので、ざっと眺めたところ、結構遊べそうな内容が書かれている。例えば記事の情報を得たければ、</p>
<p><a href="https://blog.iwh12.jp/wp-json/wp/v2/posts/428" target="_blank">https://blog.iwh12.jp/wp-json/wp/v2/posts/428</a></p>
<p>428は記事のid。「<a href="https://blog.iwh12.jp/2017/02/06/asynchronous-javascript%e3%83%97%e3%83%a9%e3%82%b0%e3%82%a4%e3%83%b3%e3%82%92%e6%9c%89%e5%8a%b9%e3%81%ab%e3%81%99%e3%82%8b%e3%81%a8srcundefined%e3%82%92%e8%bf%bd%e5%8a%a0%e3%81%99%e3%82%8bbug/">Asynchronous Javascriptプラグインを有効にするとsrc=undefinedを追加するBUG修正</a>」に相当する。またidが無ければ全ての記事が対象となる（デフォルト10件）。</p>
<p>これをJQueryの$.ajaxでやり取りすれば、本来のデザインとは違う別のページを簡単に表示することが可能だ。テストで軽く作ってみたのが<a href="https://blog.iwh12.jp/test/rest_api_test.html" target="_blank">こちら</a>。コードは以下の通り。</p>
<p></p><pre class="crayon-plain-tag">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;meta charset="utf-8"&gt;
&lt;title&gt;Wordpress REST API TEST&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div style="width: 600px;"&gt;
    &lt;div id="title" style="font-weight: 800;"&gt;&lt;/div&gt;
    &lt;div id="date" style="text-align: right;"&gt;&lt;/div&gt;
    &lt;div id="content"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"&gt;&lt;/script&gt;
&lt;script type="text/javascript"&gt;
$(document).ready(function() {
    $.ajax({
        type: 'GET',
        url: 'http://blog.iwh12.jp/wp-json/wp/v2/posts/428',
        dataType: 'json'
    })
    .done(function(data, textStatus, jqXHR) {
        console.log(data);
        $('#title').html(data.title.rendered);
        $('#date').html(data.date);
        $('#content').html(data.content.rendered);
    })
    .fail(function(jqXHR, textStatus, errorThrown) {
        console.log('fail');
    });
});
&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</pre><p>もう少し本格的なのを作ってみるかな…。</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>WordPress Popular Postsプラグインで任意のIPアドレス除外する</title>
		<link>https://blog.iwh12.jp/2017/02/13/wordpress-popular-posts%e3%83%97%e3%83%a9%e3%82%b0%e3%82%a4%e3%83%b3%e3%81%a7%e4%bb%bb%e6%84%8f%e3%81%aeip%e3%82%a2%e3%83%89%e3%83%ac%e3%82%b9%e9%99%a4%e5%a4%96%e3%81%99%e3%82%8b/</link>
		
		<dc:creator><![CDATA[knishika]]></dc:creator>
		<pubDate>Mon, 13 Feb 2017 00:43:07 +0000</pubDate>
				<category><![CDATA[日記]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Program]]></category>
		<guid isPermaLink="false">http://blog.iwh12.jp/?p=492</guid>

					<description><![CDATA[WordPress Popular Postsプラグインは、人気のある記事一覧をサイドバーなどに置けて便利なのだが、ログインユーザーしかカウントから外すことができず、スマホなど別の端末で自分が確認も含めアクセスすると有効]]></description>
										<content:encoded><![CDATA[<p><img src="https://blog.iwh12.jp/wp/wp-content/uploads/2017/02/wp_popular_posts.jpg" alt="WordPress Popular Posts" width="100%" class="alignnone size-full wp-image-493" srcset="https://blog.iwh12.jp/wp/wp-content/uploads/2017/02/wp_popular_posts.jpg 767w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/02/wp_popular_posts-300x97.jpg 300w, https://blog.iwh12.jp/wp/wp-content/uploads/2017/02/wp_popular_posts-30x10.jpg 30w" sizes="(max-width: 767px) 100vw, 767px" /><br />
<a href="https://ja.wordpress.org/plugins/wordpress-popular-posts/" target="_blank" class="external ext_icon">WordPress Popular Posts</a>プラグインは、人気のある記事一覧をサイドバーなどに置けて便利なのだが、ログインユーザーしかカウントから外すことができず、スマホなど別の端末で自分が確認も含めアクセスすると有効になってしまう。そこまで厳密にアクセスを数える必要も無いと思うが、たまたま仕事場が固定IPアドレスなので、除外することにした。<span id="more-492"></span></p>
<p>プラグインの設定で「visitors」があったので、これをキーワードにコードを検索すると該当箇所を発見。</p>
<p></p><pre class="crayon-plain-tag">// wordpress-popular-posts/wordpress-popular-posts.php 
if (
(0 == $this-&gt;user_settings['tools']['log']['level'] &amp;&amp; !is_user_logged_in()) || 
(1 == $this-&gt;user_settings['tools']['log']['level']) ||
(2 == $this-&gt;user_settings['tools']['log']['level'] &amp;&amp; is_user_logged_in())
) {
  add_action( 'wp_head', array(&amp;$this, 'print_ajax') );
  // Register views from everyone and/or connected users
  if ( 0 != $this-&gt;user_settings['tools']['log']['level'] )
    add_action( 'wp_ajax_update_views_ajax', array($this, 'update_views_ajax') );
  // Register views from everyone and/or visitors only
  if ( 2 != $this-&gt;user_settings['tools']['log']['level'] )
    add_action( 'wp_ajax_nopriv_update_views_ajax', array($this, 'update_views_ajax') );
}</pre><p> </p>
<p>ここを</p><pre class="crayon-plain-tag">if ($_SERVER['REMOTE_ADDR'] != 'xxx.xxx.xxx.xxx') { // 任意のIPアドレス
}</pre><p> </p>
<p>で囲めばいい。仕事場のルーターからログインせずアクセスしてもカウントされず、4Gでスマホからアクセスするとカウントされ、作動を確認。但しプラグインの更新で戻ってしまうので要注意！</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
