Movable Typeの最近のブログ記事

uehatsu (2015年12月18日 00:00)

これは Movable Type Advent Calendar 2015 の18日目の記事です。

先月末に開催された MTDDC Meetup TOKYO 2015 に参加してきました。色々なセッションがあり大変面白かったです。コミュニティがプロダクトを盛り上げている感がダイレクトに伝わってきて、今後のMovable Typeの展望みたいな物も参加者の方々と話せたのが自分にとっての良い経験になりました。
特に野田さんの発表は興味深く聞かせて頂きました。自分もほぼ同意の発表内容でしたが、プラグインで解決してしまうと自分が居なくなった後プロジェクトの負の遺産になる事を考えて、できる事はMTMLで済ませてしまっています。これはPerlプログラマが圧倒的に市場に足りていないという事と、継続的インテグレーションの中にMTのプラグインを入れてしまって良いかという宗教論的な部分も含んできてしまいます。ここではこの程度で終わらせます。

さて、今回はMT6の目玉とも言えるDataAPIについて書きたいと思います。先日MT Liveにて「AWSネタとDataAPIネタならどっちがいいですかね?」とSix ApartのS女史に聞いたところ、食い気味に「DataAPIっしょ」と返ってきたので、問答無用でDataAPIネタとなりましたw

まずはMTのプラグイン作成のTipsですが、こちらは私がSix Apart在籍中にまとめたドキュメントを読んで頂ければと思います。

Movable Type 開発者向けガイド - プラグイン開発ガイド

DataAPIの拡張プラグインを書くためのドキュメントはSix Apart公式の物が無いようです。自分は藤本壱さんのドキュメントを読んで勉強しました。いつも参考にさせて頂いております、ありがとうございますm(_ _)m

Data APIを使ってみる(その5) - The blog of H.Fujimoto

藤本さんも書かれていますが、config.yamlの書き方からおさらいしたいと思います。

1. config.yamlの書き方

YAMLに書くのは applications: > data_api: > endpoints: 以下になります。以下は今回の例です。

applications:
  data_api:
    endpoints:
      - id: get_hot_entries
        route: /sites/:site_id/hot-entries
        verb: GET
        version: 2
        handler: $DataAPIHotEntriesLite::DataAPIHotEntriesLite::EndPoint::HotEntries::get_hot_entries
        requires_login: 0

ここで、endpoints: 以下の要素は以下のような物を指しています。

キー内容
id エンドポイントのID
routes エンドポイントのURIのうちmt-data-api.cgi/vn以降
verb GET, POST, PUT, DELETE(HTTPのプロトコル)
version APIのバージョン
handler エンドポイントを処理する関数(ハンドラ)
requires_login ログインが必要なら1、不必要なら0

(1) id

idはエンドポイントのIDを示します。他のエンドポイントと同じ物を付けないように気を付けましょう。

(2) routes

例えば標準で実装されている get_category のroutesは以下の様になります。

/sites/:site_id/categories/:category_id

これは":site_id(ブログ、ウェブサイトのID)"と":category_id(カテゴリーID)"を指定してエンドポイントを呼び出す事になります。

実際のURIは以下のようになります。

http://www.example.com/cgi-bin/mt/mt-data-api.cgi/v2/sites/2/categories/15

ここでは:site_idを2、:category_idを15で呼び出したことになります。REST APIの標準の呼び出し方ですね。

(3) verb

HTTPプロトコルでGETやPOST、PUT、DELETEによってエンドポイントの挙動を換える事ができます。例えばGETやPOSTでは情報の取得、PUTでは作成や更新、DELETEでは削除といった風にです。最初はGETだけ指定し、必要ならPOSTを使う程度に考えておけば良いでしょう。

(4) version

DataAPIのバージョンを指定します。MT 6.2.2でのDataAPIのバージョンは3ですが、基本上位互換ですのでversionに2を指定すると、3以降でも利用出来るようになっています。私は通常2を指定するようにしています。

2を指定したら

http://www.example.com/cgi-bin/mt/mt-data-api.cgi/v2/sites/2/categories/15

3を指定したら

http://www.example.com/cgi-bin/mt/mt-data-api.cgi/v3/sites/2/categories/15

のように、mt-data-api.cgiの後に"/v2/"や"/v3/"として指定することになります。

(5) handler

エンドポイントを処理する関数(ハンドラ)を指定します。ここはプラグイン開発では基本ですね。

最初に上げた例では以下のように指定しました。

handler: $DataAPIHotEntriesLite::DataAPIHotEntriesLite::EndPoint::HotEntries::get_hot_entries

$(プラグイン名)::(モジュール名)::(関数名)という形で指定します。

(6) requires_login

エンドポイントを呼ぶのにログインが必要かどうかを指定します。単純なGETであればログインは必要ないので"0"を指定すれば良いでしょう。逆に外部のユーザーが閲覧できないような情報を扱ったり、情報を更新したりする場合はログインが必要ですので"1"を指定しましょう。

2. ハンドラの書き方

非常に単純化して書くと以下のようになります。

package DataAPIHotEntriesLite::EndPoint::HotEntries;
use strict;

use MT::DataAPI::Endpoint::Common;
use MT::DataAPI::Resource;

sub get_hot_entries {
    my ($app, $endpoint) = @_;

    my ( $blog ) = context_objects(@_) or return;

    my $ret_val;

    (do something...)

    return $ret_val;
}

1;

ハンドラは $app と $endpoint を値として取ります。

context_objects(@_) を呼ぶと、routesで指定したリソースを返します。ここでは"/sites/:site_id/hot-entries"とroutesを指定していると想定しているので、返ってくるのは":site_id"に紐付いたオブジェクトです。ここではブログ、もしくはウェブサイトのオブジェクトが返ってきます。

":foobar_id"とした場合は、"foobar"が返ってきます。これはリソースといって基本的な物は標準で用意されていますが、用意されていないリソースをroutesに指定したい場合は、別途リソースを追加する必要があります。詳しくは藤本さんのドキュメントを参考にしてみてください。

例えば、リソースを複数指定したとします。

/sites/:site_id/categories/:category_id

この場合、以下のように context_objects(@_) を記述します。

my ( $blog, $category ) = context_objects(@_) or return;

このように、用意されているリソースについては context_objects(@_) を呼ぶだけで簡単に取得できます。便利ですね。

:site_id で指定されたブログ・ウェブサイトが存在しない場合は404エラーが、それ以外の context_objects(@_) が失敗した場合は単純にreturnしていますが、これで500エラーとなります。そのあたりもDataAPIがよしなに動いてくれるのであまり悩まされる事はありません。

必要な処理を行って情報を収集し、$ret_valの中に格納。それをreturnすると自動的にJSONが構築され呼び出し元に返されます。
少し言い換えると$ret_valは配列やハッシュ、もしくは配列のハッシュや、ハッシュの配列など自由に構築でき、MT::Util::to_json()を経由してJSONとして書き出されます。

3. 少し進んだ使い方

特に requires_login を1にしたときに必要となるのが、ブログやウェブサイト、その他のオブジェクトへの権限です。

例えばファイルをアップロードする処理を行う場合に権限も無いのにアップロードできてしまったら、セキュリティホールになります。その当たりは気を付けて自分でふさぐ必要があります。

例えばアセットの情報を取得する処理をする場合、以下のようにすることになります。

package DataAPIAssetInfo::EndPoint::Asset;
use strict;

use MT::DataAPI::Endpoint::Common;
use MT::DataAPI::Endpoint::Asset;
use MT::DataAPI::Resource;

sub get_asset_info {
    my ($app, $endpoint) = @_;

    return $app->error(403) unless $app->can_do('upload');

    my ( $blog, $asset ) = context_objects(@_) or return;

    run_permission_filter( $app, 'data_api_view_permission_filter',
        'asset', $asset->id, obj_promise($asset)) or return;

    (do something...)

    return { item => $asset };
}

1;

$app->can_do('upload') をチェックすると、そのユーザーがアップロードする許可があるか確認します。許可が無い場合は403エラーを返しています。ここでは単純化のために「最低でもユーザーがアップロード権限を持っていること」としました。

その後、run_permission_filter() を呼んで、ログインしているユーザーがそのアセットの閲覧権限があるかを確認します。権限が無い場合500エラーとなります。

run_permission_filter() は少し難しくなりますのでさわり程度とします。MTのソースから permission_filter を検索してこの辺りで何をやっているか追ってみるのも良いでしょう。

4. プラグイン開発

今回、この記事を書くにあたって DataAPIHotEntriesLite というプラグインを書きました。

https://github.com/uehatsu/mt-plugin-data-api-hot-entries-lite

Google Analytics連携のされているブログの人気記事ランキング(過去7日分の集計)をリアルタイムに取得します。このエンドポイントにアクセスするとGAのAPIにアクセスしに行くので、最悪の場合API上限に達する可能性があります。アクセスの多いブログなどに設置する場合、それらの事を認識した上で配置をお願いします。このプラグインは今後機能を充実させて行く予定です。

エンドポイント例:
https://uehatsu.info/cgi-bin/mt/mt-data-api.cgi/v2/sites/3/hot-entries

5. まとめ

DataAPIはそれ自体の活用事例はいくつか出てきました。MT for iOS 愛用してます ;-)

そろそろDataAPI拡張の事例が出てきても良いのではないかと考え、今回このエントリーをまとめてみました。皆でDataAPI拡張プラグインを書いてみませんか?(^^)ノシ

Movable Type 6 本格活用ガイドブック (Web Designing BOOKS)
Movable Type 6 本格活用ガイドブック (Web Designing BOOKS) 藤本 壱 柳谷 真志 奥脇 知宏 シックス・アパート株式会社

マイナビ 2013-11-30
売り上げランキング : 133328


Amazonで詳しく見る
by G-Tools
uehatsu (2015年11月13日 13:18)

Debianをほとんど使ったことのない上野です。ちょっと仕事で使う事になったので色々と調べています。DataAPIを使える様にしたかったのですが動かず、手間取ってしまったのでログを残しておきます。

普通にnginxとfcgiwrap, spawn-fcgiをインストールします。(MySQLとかもインストールしますが、ここでは割愛)

$ sudo apt-get install nginx fcgiwrap spawn-fcgi

ここで設定ですが、nginxをちょっとうまく設定してやらないとfcgiwrap側にPATH_INFOが渡らない。

    location / {
        access_log  /var/log/nginx/foobar.access.log;
        error_log   /var/log/nginx/foobar.error.log;

        index index.cgi index.html;

        location ~ \.cgi(/|$) {
            gzip off;
            fastcgi_index index.cgi;
            fastcgi_split_path_info ^(.+?\.cgi)(/.*)$;
            if (!-e $document_root$fastcgi_script_name) {
                return 404;
            }
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include /etc/nginx/fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_param PATH_INFO $fastcgi_path_info;
            fastcgi_param PATH_TRANSLATED $fastcgi_path_info;
            fastcgi_pass unix:/var/run/fcgiwrap.socket;
        }
    }

SCRIPT_FILENAME の設定が2回ありますが、この2行でfastcgi_paramsのincludeを挟んでやらないと、うまくパラメーターが上書きできず403エラーが返ってきてしまいます。そのエラーも単に"403"と返ってくるだけ。整形もなにもされていません、たったの3バイト。仕方が無いので2行書いています。
前だけでも、後ろだけでもダメ。Debianのメーリングリストに同様の問題が報告されていたので、バグかなと思いますが、それ以上は追っていません。動いたし(苦笑)

ともかくこれで動くようになりました。めでたし、めでたし(^^)

Movable Type 6 本格活用ガイドブック (Web Designing BOOKS)
Movable Type 6 本格活用ガイドブック (Web Designing BOOKS) 藤本 壱 柳谷 真志 奥脇 知宏 シックス・アパート株式会社

マイナビ 2013-11-30
売り上げランキング : 133328


Amazonで詳しく見る
by G-Tools
uehatsu (2015年8月26日 17:04)

ちょっと案件で分からない事があったのでMT Liveにお邪魔しています。MT Liveとは「シックス・アパートのエンジニアや、Movable Type に詳しい人たちが、コワーキングスペースに集まります。MTについてちょっと質問したい、実際に画面見せたり、話したりしながら教えてもらいたい、という方はぜひお立ち寄りください。(Facebookのイベントページより)」というイベント。毎月第2・第4水曜日にやってます、もしよろしければ。

MT Live 紹介ページ(シックス・アパート ブログ)

で、今日聞きたかったことは「Movable Typeであるオブジェクトを保存する時に、そのオブジェクトに紐付けられているカスタムフィールドが画像であった場合、その画像にたいしてゴニョゴニョする」というもの。

まず自分でやって失敗した方法がこちら。

1. pre_saveかpost_saveでフック

2. $obj->meta()でmeta情報のhashを取得

3. meta情報のhashからカスタムフィールドのbasenameを取得(hashのkeyが"field.hogehoge")となる

4. MT::Metaのmetadata_by_nameでmeta情報を取得

my $field = "field." . $meta_basename;
my $class = MT->model('entry');
my $meta = MT::Meta->metadata_by_name( $class, $field );

5. meta情報のtypeを取得

my $type = $meta->{type};

ただしこれでtypeをとると"vclob"とだけ返ってきて画像かどうかはわからない。残念。

で、今日MT Liveで高山さんに聞いたところ4.以降をこうすると良いと聞く。

4. CustomFields::Field からカスタムフィールド情報を取得

my ($field_obj) = MT->model('field')->search({
blog_id => $blog_id,
basename => $meta_basename,
});

5. カスタムフィールド情報のtypeを取得

my $type = $field_obj->type();

これで$typeに"image"が格納されていたら、このカスタムフィールドは画像!!

自分で悩んでいたら解決できませんでした。

結論:MT Liveで疑問を解決!!

皆さんも是非!!!

Movable Type 6 本格活用ガイドブック (Web Designing BOOKS)
Movable Type 6 本格活用ガイドブック (Web Designing BOOKS) 藤本 壱 柳谷 真志 奥脇 知宏 シックス・アパート株式会社

マイナビ 2013-11-30
売り上げランキング : 133328


Amazonで詳しく見る
by G-Tools
uehatsu (2015年3月 5日 00:35)

ほほう、このシンプルさはいいかも。


uehatsu (2014年3月 7日 14:02)

弊社ワンダーツーのブログメディア「ワンダードライビング」が、シックス・アパート様の事例紹介に掲載されました。「ワンダードライビングがMovable Typeを使う理由」

why_to_use_the_mt.pngこの記事のなかで、flickrにアップした画像(外部画像)をアセットとして登録する方法について問い合わせがありましたので、こちらで技術的な事をまとめてみたいと思います。

まずワンダードライビングでは先日リニューアルを行い、エントリーに画像がある場合は必ずアセットに設定するようになりました。また、そのアセットの中でも記事を代表する画像をカスタムフィールドに設定するようにしています。これにより、カテゴリーアーカイブなどで記事のサムネールが表示されるようになっています。

しかしリニューアル前の過去記事には、このアセットもカスタムフィールドも設定されておらず、カテゴリーアーカイブなどにはその際「ダミー画像(?)」が表示され、ちょっと寂しい感じになっていました。

そこで「過去記事にもサムネールを!!」というオーダーが入り、真っ先に考えたのがData APIを使う事。別にData APIである必要も無かったのですが、やはりアセットのアップロード機能があるのは嬉しい。これを利用しない手はないなという訳でさくっと作ってみました。

まずData APIを使ってエントリーの本文と続き、アセットを取得します。

アセットが設定済みならそのエントリーは飛ばし次へ。未設定ならつづいて処理を行います。

本文と続きからflickrのアドレスを検索し画像があればそのURLを検出します。

flickr画像をURLからダウンロードします。

ダウンロードした画像をData APIを使ってアセットとしてアップロードします。

そのアセットを画像に紐付けます。これはData APIの機能に無いので、プラグインで拡張しました。

その後、アセットに設定した画像をカスタムフィールドに設定します。

ここまでの処理を全エントリーに対して行うと、設定されていなかったアセットやカスタムフィールドが設定されるという具合です。

と、ここまで書いて、この内容以前のエントリーに書いているのに気づきました。あーw まぁ、いいや(^^;)

p.s. 先日、藤本さんのData APIハンズオンに参加してきました。今度名古屋でもあるとの事。Data APIに興味があって東海地区にお住まいの方は参加してみてはいかがでしょうか?

Movable Type 6 本格活用ガイドブック (Web Designing BOOKS)
Movable Type 6 本格活用ガイドブック (Web Designing BOOKS) 藤本 壱 柳谷 真志 奥脇 知宏 シックス・アパート株式会社

マイナビ 2013-11-30
売り上げランキング : 80852


Amazonで詳しく見る
by G-Tools

uehatsu (2014年1月11日 22:22)

先のエントリーでData APIのGA PVをとるところではまっていると書きました。今回はそれの原因が特定できたのでまとめてみます。

まずは自分が前回書いた内容は全部ハズレだったという事。いやぁ勘がにぶったなぁ。

戻り値のJSONをもうちょっと良く見てみたら、なんと同じURLを持ったitemが2つあるものが。一つはtitleが正しくセットされているもの。もう一つはtitleが(not set)となっているもの。

もうちょっと調べてみるとGA APIを叩いた時に既にtitleが正しいものと(not set)の物があるみたい。これはGAのjava scriptがページ閲覧時に読み込まれた時に通常は正しくtitleがセットされるのに、何かしらの理由でセットされない状況があるのかな。そうすると、同じURL(path)に対して2つのitemが戻ってきてしまう。

MT::DataAPI::Endpoint::Statsの中ではURL(path)はユニークなものとして設計されており、ハッシュのキーとしても用いられています。データのitemのポインタをURL(path)を使って指定しているため、上位から順番に$data->{items}のオブジェクトを指定していくと、titleが正しくセットされたitemではなく、(not set)となっているitemがそのURL(path)のオブジェクトとして$items{$path}に指定されてしまいます。

そうすると、archiveTypeやentry_idなどはtitleが正しくセットされている上位のitemオブジェクトではなく、正しくない(not set)となっている下位のオブジェクトに代入されます。これにより、上位にarchiveTypeがundefになっているオブジェクトが散見されるようになるという事です。

limitを200から30にして一見状況が改善されたように見受けられたのは、30位以内にtitleが(not set)となっているitemがなかったため。limitを増やせばそれがだんだん増える訳で、またPVが多ければそれだけ(not set)となる確立が高くなるため上位にundefになる物が増えていくという状況の説明にもピッタリと当てはまります。

(そしたらPerlデバッガで直接叩いても状況は変わらないはずだけど、、、見間違い??、、、)

根本的な解決としては、titleが(not set)となっているitemは、正しくセットされている物にその分のPVを加算し、$data->{items}から削除するのが良いのかなと思うのですが、ちょっとヘビー。そこで次点としてtitleが(not set)となっている物は無視するという風にコードをいじってみました。(80行目前後)

-$items{$path} = $i;
+$items{$path} = $i unless $i->{title} eq '(not set)';

これでOK。limitを200件にしても上位itemのarchiveTypeがundefになる事はなくなりました。

この方法だと(not set)となっているPV数が多くなるとランキングに若干の差が出てしまうのですが、そこまで厳密なランキングではないので、このような変更でとりあえず良しとしました。

方法は問いませんが、コアでも修正される事を願っています(^^)

追記)コアでも修正されました。私が修正したのよりも美しく修正されていますw。
https://github.com/movabletype/movabletype/commit/3dd6d275e98894afa973e3311fda2b14fc179a8a

Movable Type 6 本格活用ガイドブック (Web Designing BOOKS)
Movable Type 6 本格活用ガイドブック (Web Designing BOOKS) 藤本 壱 柳谷 真志 奥脇 知宏 シックス・アパート株式会社

マイナビ 2013-11-30
売り上げランキング : 80852


Amazonで詳しく見る
by G-Tools
uehatsu (2014年1月10日 15:21)

昨日の晩Skypeで「ランキングスクリプトに最新記事が反映されていないんだけど様子見て」と言われさっそく調査。

GAのPVを取ってくるためにpageviewsForPathを叩いていますが、この返ってくるJSONの内容がおかしい事がすぐ分かりました。

具体的にはこんな感じでarchiveTypeやentryなどの情報が全部undefで返ってきてる。

$VAR1 = {
'totalResults' => 886,
'items' => [
{
'entry' => undef,
'pageviews' => '311',
'author' => undef,
'category' => undef,
'archiveType' => undef,
'title' => "\x{30aa}\x{30fc}\x{30c8}\x{30b5}\x{30ed}\x{30f3}2014\x{3001}\x{4eca}\x{5e74}\x{3082}\x{5343}\x{8449}\x{770c}\x{8b66}\x{304c}\x{4e00}\x{6589}\x{53d6}\x{7de0}\x{3092}\x{5b9f}\x{65bd}\x{3010}\x{30ef}\x{30f3}\x{30c0}\x{30fc}\x{30c9}\x{30e9}\x{30a4}\x{30d3}\x{30f3}\x{30b0}\x{3011}",
'path' => '/archives/2014/01/2014-autosalon-police.html'
},
(後略)

本当ならここはentryにentry_idが、archiveTypeにIndividualが入っていないと駄目なはず。これが入っていないのでスクリプトでは個別アーカイブとして認識出来ず、当該のエントリーを抜かしていたという訳。これはDataAPIの挙動に問題がありそう。

pageviewsForPath Endpointは、個別ページだけでなく、インデックスやカテゴリーアーカイブが入るため実際にランキングで取る数より多く件数を取っています。これがいけないのかと思い多めにしていたlimitの数を30前後まで減らしてみました。そうするとデータが取れるようになった。

$VAR1 = {
'totalResults' => 887,
'items' => [
{
'entry' => {
'id' => '1430'
},
'pageviews' => '312',
'author' => undef,
'category' => undef,
'archiveType' => 'Individual',
'title' => "\x{30aa}\x{30fc}\x{30c8}\x{30b5}\x{30ed}\x{30f3}2014\x{3001}\x{4eca}\x{5e74}\x{3082}\x{5343}\x{8449}\x{770c}\x{8b66}\x{304c}\x{4e00}\x{6589}\x{53d6}\x{7de0}\x{3092}\x{5b9f}\x{65bd}\x{3010}\x{30ef}\x{30f3}\x{30c0}\x{30fc}\x{30c9}\x{30e9}\x{30a4}\x{30d3}\x{30f3}\x{30b0}\x{3011}",
'path' => '/archives/2014/01/2014-autosalon-police.html'
},

元々は200件取得して、30件の個別ページを一覧で出していたのですが、仕方が無いので30件取得して20件の個別ページを一覧で出すようにスクリプトを修正してとりあえずはクリアしました。

しかしこれだけじゃ、何のナレッジもたまらない。そこでさらっと仕組みを調査してみる事に。

pageviewsForPathが呼ばれると実際に叩かれるのは MT::DataAPI::Endpoint::Stats::pageviews_for_path()

この中でMT::DataAPI::Endpoint::Stats::fill_in_archive_info() を呼んでいます。

fill_in_archive_info() の中身を覗いてみるとそのものズバリ、ArchiveTypeなどをセットしている場所でした。この中でGA APIから返ってきている情報をarchive_infoとしてmt_fileinfoの中身と突き合わせをしています。

archiveTypeをセットしているのは、$fi->archiveTypeがdefinedの時。つまりこの場所を通っていてarchiveTypeが設定されていないという事はmt_fileinfoのデータの中にarchiveTypeが設定されていない物があるという事。

そこでデータベースを覗いてみると、archiveTypeがnullになっている行は一切なく、逆説的にarchiveTypeがundefになっているarchive_infoが出力されているという事は、mt_fileinfoのイテレータ $iterの中に当該のエントリーの情報が入っていない、MT->model('fileinfo')->load_iter()の条件式が誤っているという事になります。

内部処理で使われているURL一覧の行列 @in_pathが多くなりすぎてD::ODが処理しきれていないのか、別の問題か、、、

で、ここでタイムアップ。これ以上は僕の仕事ではないや:-P

以前もあったlimitを多くしすぎると尻切れとんぼのJSONが返ってくる問題や、今回の仕様と違った形のJSONが返ってくる所など、一人のユーザーとして調査しきれる内容のものではないので、一日もはやく修正される事をのぞんでおります。

p.s. ちなみにPerlデバッガーから直接MT::DataAPI::Endpoint::Stats::pageviews_for_path() を叩くのを試してみたところ、limitを200にしようが300にしようが正しい形のデータが取得出来ました。これってApache CGIとして動かした時に問題が発生するという事でしょうか。だとしたら、もっと根が深いなぁ、、、

Movable Type 6 本格活用ガイドブック (Web Designing BOOKS)
Movable Type 6 本格活用ガイドブック (Web Designing BOOKS) 藤本 壱 柳谷 真志 奥脇 知宏 シックス・アパート株式会社

マイナビ 2013-11-30
売り上げランキング : 80852


Amazonで詳しく見る
by G-Tools
uehatsu (2014年1月 6日 15:59)

年が明けて数日が立ちました。暦上は皆さん今日から仕事はじめでしょうか?本年もよろしくお願いします。

さて、年末年始関係なくData APIのプラグインやスクリプトを書きまくっている上野ですが、年始に実家に寄ろうと移動中、実家近所の駅近のタリーズでMT開発チームの元同僚と偶然会うなど、年明け早々Movable Type漬けになりそうな雰囲気でございます(笑)

さて、今日のエントリーもData APIネタ。Movable TypeのData APIで本当に作る必要があったのか微妙ですが、うちのブログメディアは基本flickrに上がっている画像を引っ張ってHTMLに貼付けています。その中のこれという画像を個別にアセットとしてアップロードし、メイン画像としてカスタムフィールドに放り込むとMTMLで色々と画像が処理される仕組みになっています。

この仕組み、今からアップするエントリーは個別にその作業をすれば良いのですが、今まで書いて来た数百件にのぼるエントリーに改めてこの作業をするのは骨が折れます。そこでData APIの出番。スクリプトでがりっとやってしまおうというのが今回のお題です。

まず作ったのは今までのスクリプトの派生としてData APIを使って全エントリーを取得するだけのスクリプト。さすがに数百件のエントリーをいっぺんに取得するのはメモリーの無駄遣いなので、offsetとlimitを使って適当な数で区切って読み込むようにしました。読み込む内容は、'id,permalink,body,more,assets,customFields'をfieldsにセットしてやります。これで、エントリーID、エントリーURL(permalink)、エントリーtext、エントリーmore、アセット情報、カスタムフィールド情報、が取得出来ます。

最初、エントリーtextを取得するのに fields => 'text' なんてやっていたものだから取得出来ず、悩んだ挙げ句リファレンスを読んだら fields => 'body' なんだとか。ちょっとビックリ。

これで数十件まとめて読んだところ、MT::Util::from_jsonがエラーを吐いた。取得したjsonを見てみると途中で切れてる。取得するjsonに容量制限ってあったのかしら?仕方がないので、エラーを出さない範囲(余分を見て5件)毎にしました。

bodyとmoreを連結して、HTML::TagParserで<img>タグを取得。そのsrcを見て、flickr.comが含まれていたら外部サイトにある画像だと認識。

画像ファイルを一旦 /tmp/ 配下に保存してData APIのアセットアップロード機能を使ってアップロード。ここで HTTP::Request のPOSTでファイルをアップロードするのにちょっとしたテクニックが必要でした(詳細は別の詳しいサイトにまかせます)。戻り値としてアセット情報が返ってくるので、このアセットIDを保存しておきます。

今度はアセットをエントリーに紐づける必要があるのですが、Data APIにはその機能がありません。そこで、アセットのREST APIにentry_idを放り込むと紐づけてくれるエンドポイントをプラグインとして追加しました。具体的には内部で MT::ObjectAsset->get_by_key() を呼んでやり、エントリーにアセットを紐づけます。

通常であればここまでで良いのですが今回はその情報をカスタムフィールドに設定しなければなりません。そこでData APIのエントリーアップデート機能を使ってカスタムフィールドを更新します。画像カスタムフィールドは内部で画像以外の値(<form>タグなど)を持っているので、それにあわせて整形しアセットのURLとIDをセットしました。

このエントリーのアップデートで困ったのが Can't call method "errtrans" without a package or object reference at lib/MT/CMS/Entry.pm line 2266. というエラーが頻発した事。処理自体は最後まで行っているのでリビルドの際に何か問題が起きているのかもしれませんが、深くは追いませんでした。時間があれば調べてみます。今回はスクリプトの再実行で済ませました。

こんな風にした事で、過去のエントリーで使った画像もサムネールとして表示されるようになりました。ここまでやるのであれば別にData APIではなくてMT::Appから派生させれば良いのですがまあ良しとします。おかげでData APIの内部構造もかなり分かってきましたし、MT::Appから作成するよりも画像のアップロードなどは簡単にできますし、他にも色々な面で深く考える必要がないので短時間で実装が可能でした。

あとはスクリプトの内部にID, PASSWORDをもたないといけない点だけをクリアしたいです。accessTokenを生成するための内部コードを書けばよさそうですね。また今度考えてみます。

Movable Type 6 本格活用ガイドブック (Web Designing BOOKS)
Movable Type 6 本格活用ガイドブック (Web Designing BOOKS) 藤本 壱 柳谷 真志 奥脇 知宏 シックス・アパート株式会社

マイナビ 2013-11-30
売り上げランキング : 80852


Amazonで詳しく見る
by G-Tools

uehatsu (2013年12月31日 19:21)

前回のエントリーでData APIを使ってGoogle Analyticsと連携したPVランキングを取得するスクリプトを作成しました。

ここまではData APIの実装をこねくり回せばなんとかなったのですが、1位から30位までのランキング一覧を作成するにあたって、MTMLで作成しているアセット画像のサムネールのURLを取得する必要が出てきました。

アセットのサムネールには生成する時に独自の命名規則があるので、それをガリっと正規表現で書いてやる事もできるのですが(最初はこの方法で実装していた)、そのサムネールが存在しなかった時に画像が表示されず「画像が無い場合はデフォルト画像を出してね」的なjQueryを書いてやるなどしないと都合が悪く、ちょっとなんだかなと思っていました。(もちろん、スクリプトの中でUAを作りその画像にアクセスできるかどうか確認する方法もあります)

で、最終的に考えついたのが「だったらサムネールを自動生成するDataAPI拡張プラグイン作っちゃえばいいじゃん」というもの。早速壱さんのブログを検索しData API拡張プラグインをゲットし内部構造を勉強。30分ちょっとで書けました。

まず基本はアセットのIDをData APIに渡すとその情報を取得出来るようにするプラグインを書きました。

アクセスポイント)http://www.example.com/cgi-bin/mt/mt-data-api.cgi/v1/site/3/assets/53/info

最後のinfo付けようかどうか迷ったんですが、名前空間的に他の方のプラグインとぶつかりそうだったのでこんなURLにしました。これ最後まで悩んだw

で、このURLにアセットのID(ここでは53)を渡すと、アセットの情報が取得出来るようにしました。

この後、thumb_width, thumb_height, thumb_square, thumb_scaleなどをGETで渡して情報としてJSONに格納出来るように拡張。このときはまったのが、$asset->{thumb_width}に値をセットして返って来るかと思いきや返ってこず。どうも基本はカラムやカスタムフィールドを返すようになっているようで。ここは深くは追わなかったのですが、独自の値を返す時にオブジェクトのカラムと同様に扱える方法があったらどなたか教えて下さい。resourcesを拡張してもできませんでした。

で深く追っても仕方が無い(?)ので、戻り値としてアセットの情報をitemに、サムネールの情報をthumb_infoに格納して返すように変更。

どうしようかとも思ったんですが、ある意味汎用的である必要はないので今回はこのような実装にしました。

ここまで出来たら後はサムネールの情報を返すまで。サムネールの情報を返すには「mt:Asset」タグをMTMLで使うのが簡単。という訳でテンプレートでmt:AssetThumbnailURLを返すようにして、プラグインからテンプレートのbuild_pageを呼んでやります。

まぁ、こんな感じ。

<mt:var name="asset_id" setvar="asset_id"><mt:var name="thumb_width" setvar="width"><mt:var name="thumb_height" setvar="height"><mt:var name="thumb_square" setvar="square"><mt:var name="thumb_scale" setvar="scale"><mt:Asset id="$asset_id"><mt:if name="thumb_scale"><mt:AssetThumbnailURL scale="$scale" /><mt:else><mt:AssetThumbnailURL width="$width" height="$height" square="$square" /></mt:if></mt:Asset>

これで戻ってくる値には改行が含まれるので、chompしてやってthumb_urlとして返してやります。

あとは、この値を$asset_content->{thumb_info}->{thumb_url}のように呼んでやれば、既にサムネールが存在している場合はそのURLを。存在しない場合はサムネールを作成した上でそのURLを返してくれるようになります。うーん、Data APIさまさまなこの実装。

最後に、権限チェックを実装して終わり。これで人気記事ランキングの一覧作成スクリプトが完成しました。

もっと手こずるかと思っていたData APIの拡張プラグイン。参考に指して頂いた壱さんのコードがすっきりとしていた事もあり、スムーズに実装が出来ました。さて、もっと仕事しないとw

Movable Type 6 本格活用ガイドブック (Web Designing BOOKS)
Movable Type 6 本格活用ガイドブック (Web Designing BOOKS) 藤本 壱 柳谷 真志 奥脇 知宏 シックス・アパート株式会社 

マイナビ 2013-11-30
売り上げランキング : 80852


Amazonで詳しく見る
 by G-Tools
uehatsu (2013年12月27日 15:20)

Movable Type 6.0の目玉機能として実装されたData API(平田さんの最近の記事はここ

このData APIには、こちらもMT6で実装されたブログページとGoogle Analyticsを連携させる機能を使い、簡単なREST APIを叩くだけでGoogle AnalyticsのPVランキング情報をJSONの形で取得する事ができます。このJSONデータをperlやPHPなどでパースしHTML形式に吐き出したり、そのままの形でJava Scriptで整形処理する事も出来ます。

しかし、Data APIには認証を必要とするメソッドと、そうで無いものがあり、Google AnalyticsのPV情報を取得するには認証が必要になります。一般のユーザーが認証無しにGoogle Analyticsの情報を取得する事は出来ないため、Data APIのJava Script SDKなどを使ってJava Scriptで整形する事は物理的に不可能です。そこで今回はMTモジュールから派生させたMTアプリケーションとしてスクリプトを作成しました。

具体的には以下のブログメディアの「人気記事ランキング」がそれです。

 http://wonderdriving.com/

1. Data API利用の基礎

繰り返しになりますが、Data APIはRESTによるGETもしくはPOSTでのやり取りとなります。Data APIから戻ってくる値は全てJSON形式です(APIドキュメント

ドキュメントに書かれているように特定のURLにBlog IDやメソッド名などを付けてHTTPでアクセスします。

例)http://www.example.com/cgi-bin/mt/mt-data-api.cgi/v1/sites/3/entries
 ここで"3"はBlog ID。Blog ID 3のブログのエントリー一覧を取得します。

2. 認証の有無

先ほども説明した認証ですが、Google Analyticsの情報取得やエントリーの投稿など権限が無ければいけないものに必要です。その他にもエントリー一覧などは認証しないでアクセスすると公開済みエントリーだけが表示され、認証した上でアクセスすると未公開や日時指定投稿のエントリーも表示される等、認証の有無で挙動が変わるメソッドもあります。

3. 認証の方法

MTのUser名とパスワードを使ってAuthenticationを呼びます。ここでアクセストークンが帰ってくるので、認証が必要なメソッドを呼ぶ時は、このアクセストークンをHTTPヘッダーにセットしてData APIを叩きます。

例)http://www.example.com/cgi-bin/mt/mt-data-api.cgi/v1/authentication
 username, password, clientIdをPOSTします。
 戻り値の"accessToken"にアクセストークンがセットされて返ってきます。

アクセストークンをセットするHTTPヘッダーは以下の様になります。

例)X-MT-Authorization: MTAuth accessToken=${accessToken}

これでGoogle Analyticsの情報を取得する準備ができました。

4. pageviewsメソッド

Google Analyticsの情報を取得するメソッドはいくつかありますが、PVランキングを得るにはpageviewsメソッドが適しています。このメソッドに情報を取得する期間をstartDate, endDateで設定(形式はYYYY-MM-DD)します。

このメソッドにはlimitプロパティもあり、そこに数字をセットするとその件数分ランキング情報が取得出来ます。この情報には個別アーカイブだけでなく、カテゴリーアーカイブやインデックスページのPVもカウントされています。今回は個別アーカイブのみをランキングの対象にしたいため、limitは本当に取得したい件数よりも多めに設定しました。

5. 取得出来る情報

このメソッドの戻り値の中には、以下のような情報が入っています。

  • 個別アーカイブのEntry ID
  • 指定した期間中のPV数
  • AuthorアーカイブのAuthor ID
  • カテゴリーアーカイブのカテゴリーID
  • archiveType (Indivisual, Category, ...)
  • タイトル
  • パス(URLのうちホスト名より以降、/index.html等)

情報がどのタイプかはarchiveTypeをみれば分かります。今回は個別アーカイブなので"Indivisual"のものだけをピックアップしました。

6. アクセスランキングを作るのに必要な情報

これらの情報からだけでもアクセスランキングを作る事は可能ですが少し情報が少ないため、味気ないランキングになってしまいます。そこで今回はData APIの他のメソッドも使う事でより多くの情報を取得しました。

まず個別アーカイブにはEntry IDが入ってくるため、このEntry IDをキーにエントリーの詳細情報を取得します。

例)http://www.example.com/cgi-bin/mt/mt-data-api.cgi/v1/sites/3/entries/1201
 Entry ID 1201の詳細情報を取得します。このままアクセスすると本文等必要以上の情報が返ってきてしまいます。
 そこで戻り値を制限するためにfieldsプロパティに'title,categories,date,permalink'を指定します。

この様にfieldsプロパティを設定する事で、タイトルとカテゴリー、記事日時とパーマリンクが取得出来ます。タイトルとパーマリンクはPV情報を取得した時にも得られましたが、タイトルについてはUTF8 Encodeの関係で、パーマリンクはhttpから含めてフルパスで欲しいためにあえてもう一度取得しています。

7. カテゴリー

上記の方法でカテゴリーも取得出来ます。(PV情報を取得した時には個別アーカイブにはカテゴリーIDが入ってきませんでした。入っていてもカテゴリーIDだけではあまり意味はありませんが)

カテゴリー情報はカテゴリーラベル文字列の配列で返ってきます。階層は"/"区切りで示されているため、ラベルに"/"さえ入っていなければ、splite関数で階層構造を取得する事が可能です。

さらに、カテゴリーアーカイブのパスを取得するために、Data APIでカテゴリー情報の取得しbasenameからパスを作成してやります。

例)http://www.example.com/cgi-bin/mt/mt-data-api.cgi/v1/sites/3/categories
 カテゴリーのラベルとbasenameが取得できるのでHASHに保存しておき、あとでまとめてアーカイブパスを生成します。

あとはテンプレートを作って、そこに情報を書き込めば終了です。このスクリプトを1時間に1回程度cronを使って回し、その結果をSSIやPHPインクルードなどを使ってホームページに貼付けます。(もちろん即時性が必要なければビルドして静的なファイルとして吐き出す事も可能です)

JSON関係などでMT::Utilを使いたいために今回のスクリプトはMTアプリケーションとして作成しましたが、そこを自前で書くのであれば全く必要性はありません。また、perlだけでなくPHPやrubyといった他の言語でもスクリプトの作成が可能です。以前のエントリーでData APIを使ってiOSアプリを作成しましたが、Data API自体はシンプルなREST APIなのでどんな言語・プラットフォームからも活用する事ができます。まだまだ可能性を秘めているData API。これからも積極的に使って行きたいと思います。

p.s. ランキングスクリプトやData APIを用いたスクリプト・プラグインについてご興味のある方はご相談下さい。

Movable Type 6 本格活用ガイドブック (Web Designing BOOKS)
Movable Type 6 本格活用ガイドブック (Web Designing BOOKS) 藤本 壱 柳谷 真志 奥脇 知宏 シックス・アパート株式会社

マイナビ 2013-11-30
売り上げランキング : 80852


Amazonで詳しく見る
by G-Tools

Who is uehatsu?

uehatsu

アーカイブ

Facebook page