ハッカソン素振り成果

マネーフォワードのAPIを提供したハッカソンに技術サポート兼デモサーバーのおもり兼メンター代打として参加してきた。

OAuth2といっても、使い方までいたれりつくせりのtwitter/facebook/github あたりのライブラリしか使ったことなかったので、一定時間で作れる範囲がだいぶ違うなーという感想。そりゃ世間のハッカソンも、ハッカソンという名のチーム戦・短納期デスマーチになるわ。

OAuth2API経由で触ったことないから、素振りしてみないと全部わかりませんで話にならんだろう、ってことで素振り。
ただ、ギョームに追い詰められて、当日含めてもtoken取るところまで疎通確認するのでやっとだった。

ハッカソン後日にようやく出来て、おもしろおかしい成果物animetion gifが撮れてほっとした。
game-with-transactions
おもしろおかしく書いたエントリ: Moneyforward APIで車ゲームを作る | Money Forward Engineers’ Blog

構造を知っている、入出金履歴のデータを引っ張ってくる。
日にちごとにサマリーして、ない日は0で埋める。
それを元データにして座標プロットして、phaserjsのexampleの座標データと差し替える。
たったこれだけやるのに、56 commits / 4,571 ++ / 398 —

仕様はOAuth2でわかりやすい、データ取りに行くはじめのところはsimple-oauth2のexampleからコピペ、ゲーム部分はphaserjsのexampleからコピペ、でほぼ何もしてないのに結構時間はかかるわ、$40の有償プラグイン使うわで、しんどかった。
デモサーバー自体は多分クローズドだから、サーバー立てておいてみんなに遊んでもらうわけにもいかないし。
遊べたとしてもみんなデモデータだからなー。

作り始める時には意図しなかったいいオチが勝手についてくれた幸運と、読んでくれた人は楽しんでくれたみたいなので救われる。

成果物 https://github.com/sanemat/purple-finch とpackage.json(最後)。

ライブラリは提供していないので、simple-oauth2を使い、それを使ってるライブラリを見ながらぱくる。
なぜか手元でdirenvが動かなくて、dotenv使うことにしたのがはまったとこ。
テンプレートにmarkojs使ってみたのは初めて。
https://github.com/marko-js/templating-benchmarks pugかejsか使おうと思ってたところでこれ見て使ってみることにした。
node --inspect --debug-brkってのもおもしろい。サーバー起動すると、websocketのurlが出て、それをchromeで開くと一行目で止まる。
ブレークポイントを入れて、play!
ゲームエンジンphaserjsも、なにかゲームエンジン使ってみたかったので多少は満たされた。カジュアルゲー作れるぐらいの瞬発力は欲しいもん。
github pagesに置いて動くようにしたいところだけど、implicit flowじゃないからだめなんかな。
aws lambdaやamazon api gatewayかそういうの使えばgithub pages + それで出来そうな気がしたけど、気がするところまで妄想しただけで確認していない。こういうのAmazon API Gateway の Custom Authorizer を使い、OAuth アクセストークンで API を保護する – Qiita かな。

package.json

{
  "dependencies": {
    "dotenv": "^2.0.0",
    "express": "^4.14.0",
    "marko": "^3.12.1",
    "moment": "^2.17.0",
    "moment-timezone": "^0.5.9",
    "request": "^2.79.0",
    "request-promise-native": "^1.0.3",
    "simple-oauth2": "^1.0.1",
    "urijs": "^1.18.3"
  },
  "scripts": {
    "start": "node -r dotenv/config app.js",
    "debug": "node --inspect --debug-brk -r dotenv/config app.js",
    "lint": "eslint .",
    "lint:fix": "eslint --fix .",
    "test": "ava",
    "verify": "npm run lint && npm test"
  },
  "devDependencies": {
    "ava": "^0.17.0",
    "eslint": "^3.10.2",
    "eslint-config-airbnb": "^13.0.0",
    "eslint-plugin-import": "^2.2.0"
  }
}
広告

Rubyのハテナってbool返すんじゃないの?

rubyのNumeric#nonzero?ってself | nilを返すんだ。ハテナってbool返すんじゃないの?
って聞かれた。
自分もこの前までそう思ってたんだけど、たしかそうじゃない便利ケースがAPIデザインケーススタディに出てきた、
ってうろ覚え知識を披露した。なにの時便利だったかは覚えてなかったので、ふわふわ。
なので調べた。

instance method Numeric#nonzero? (Ruby 2.3.0) http://docs.ruby-lang.org/ja/2.3.0/method/Numeric/i/nonzero=3f.html

Rubyではnilとfalseが偽、それ以外の値は全て真とみなされるので、このInteger#nonzero?の振る舞いは、0に対して偽、
それ以外に対して真を返すものと言えます。つまり名前に反する動作というわけではありません。
(snip)
意外な動作は学習コストを上げるので、理由がなければ避けるべき
(snip)
Enumerable#sort メソッドに与えるブロックの中の記述を簡単にするため。
APIデザインケーススタディ 田中哲

enum.sort {|a, b| c = a.x <=> b.x; c != 0 ? c : a.y <=> b.y }
が
enum.sort {|a, b| (a.x <=> b.x).nonzero? || a.y <=> b.y }

と記述できる。

具体的には自分でハテナつきメソッドを書くとき、気を使うときは、
わざわざtrue/false を返すように、最後絶対bool返すぞ!って foo ? true : falseって書くことはある。
どこかで何かを読んで、しかも最近、必ずしもそうしなくて良くて、
truthy/falsy 返すだけで名前に反するわけではない、と何かで読んだ。
そのなにかがAPIデザインケーススタディだった。

別のものを返すことで使いやすいケースだったりそういう使い方しかないケースでは、bool以外が返ることがある。
だったはずだから、ドキュメントにあるはず。

documentみていく。
ObjectやEnumerableにはbool返すのしか生えてなかった。

Numeric#nonzero? -> self | nil
Encoding.compatible?(obj1, obj2) -> Encoding | nil
Kernel.#autoload?(const_name) -> String | nil

だんだん見ていくとだいたいboolなんだけど、いくつかboolじゃないものもあった。
上記は全部ではなく、ぱっと見て出てきたもの。

ほぼだいたいboolだがそうでもないのもいるから、truthy/falsyで判別すればびっくりしなそう。

APIデザインケーススタディ読んだ

ようやく積んでた本を読んだ。読んだというか、途中まで読んでてきつくなったのでパラパラ眺めてどうにか一周終わらせた、ってかんじ。話の内容は多分面白くて書いてある中身もきっと面白いんだけど、対象に今の自分があまり興味がなかったから仕方ない。

APIデザインケーススタディ ――Rubyの実例から学ぶ。問題に即したデザインと普遍の考え方 | Gihyo Digital Publishing … 技術評論社の電子書籍

第1章─I/O, 第2章─ソケット, 第3章─プロセス, 第4章─時刻, 第5章─数,文字列 とあって、要は自分が一番好きなプレゼンの使いやすいライブラリ API デザイン. 日本Rubyカンファレンス2006 コレと近い内容なんだけど、I/O, ソケット、プロセス、時刻まであんまり興味がないので、その、あれ。

URI.encode_www_form出てきてやっと興味あるとこキタ! と思ったらそれが最後でしょんぼり。

なので、あとは思い出したことを並べる。

使いやすいライブラリAPIデザインから、自分は強烈な印象を受けていて、過去の自分の名作 https://github.com/sanemat/slip/blob/b49f8a609741c29054cacf0fe84a2a6d377bba53/slip.rb#L14 に色濃く出ている。

脱線すると、httpライブラリの好みの変遷としては、何使ってたか覚えていない -> 感銘を受けて open-uriに -> httparty で楽して楽しく -> REST厨なのでrest-client -> faraday でインターフェースを統一するんだ -> net-http やっぱり標準ライブラリだね、あとeasy, simpleだとsimpleよりのがいい イマココ

コレも好き。matz を説得する方法. RubyKaigi2008

さがしてまわっちゃったけどここにあった。Publications of Tanaka Akira

Faraday と http://example.com/?data=1&data=2

getのパラメーターで同じ名前の値を複数渡した時のFaradayの挙動の話。テストとissue読んで実装は見てない。コーナーケースだけどopen socialやoauthの認証周りだとよくある話。Faradayに http://example.com/?data=1&data=2 をリクエストさせようとするとhttp://example.com/?data[]=1&data[]=2 になる。

具体的には google-api-ruby-client のテストが一つそこで落ちてて、原因探しに行っ
たらFaradayのissueでなおかつ議論して放置されてる。

Faraday handles repeated query parameters incorrectly ?? Issue #182 ?? technoweenie/faraday https://github.com/technoweenie/faraday/issues/182
Show warning when :bracketize_params is set incorrectly. ?? Issue #5 ?? google/google-api-ruby-client https://github.com/google/google-api-ruby-client/issues/5

普段アプリに組み込んだりして使うぶんにはデフォルトbracket動作が便利 オブジェクトにマッピングしやすい なんだけど、option指定したらそうじゃない動作してほしくて、そのオプションがちゃんと動いてない。で止まってる。
ちょろちょろっと直すには重い。メジャーライブラリの動作に未実装なところがあった時に、しかし自分たち側のライブラリのテストpendingにするでもなくredのままにしとく度胸?信念?は強いなあ。

Faradayについてはこちら:
Ruby の HTTP クライアントライブラリ Faraday が便利そう Gist https://gist.github.com/2775321

2chのスレッドタイトルをクラウド化 threadTitleCloud b0.1.0

Yahooの日本語形態素解析Webサービスを利用して、2chのスレタイをタグクラウドに投げ込んでみました。

作りながら妄想してたほどには面白くなかったです。うーん。HTML/TagCloudの使い方をもう少し勉強するなり、閾値上げてみるなりして、視覚的面白さを優先した方がいいのかも。プルダウンで閾値弄れるといいのかも。まあとりあえずベータ0.1.0ってことで。

参考
Yahoo!デベロッパーネットワーク – テキスト解析 – 日本語形態素解析 http://developer.yahoo.co.jp/jlp/MAService/V1/parse.html
ヤフー、文章を解析できるAPI「日本語形態素解析Webサービス」を公開:ニュース – CNET Japan
http://japan.cnet.com/news/media/story/0,2000056023,20351038,00.htm
Do You PHP はてな – Yahoo!の日本語形態素解析Webサービスを使ってTwitterで流行っているキーワードをクラウド化
http://d.hatena.ne.jp/shimooka/20070618/1182163865

コード

ライセンスはApache License, Version 2.0です。licence.txt

<?php
/**
* threadTitleCloud b0.1.0
* Copyright 2007 sanemat
*
* 参考
* Do You PHP はてな - Yahoo!の日本語形態素解析Webサービスを使ってTwitterで流行っているキーワードをクラウド化
* http://d.hatena.ne.jp/shimooka/20070618/1182163865
*
*/
/* 準備 */
date_default_timezone_set('Asia/Tokyo');
mb_internal_encoding('UTF-8');
require_once('HTTP/Client.php');
require_once('HTML/TagCloud.php');
$target = '[対象の板]';
$applicationID = '[Yahoo! DEVELOPER NETWORKのアプリケーションID]';
$ua = 'Monazilla/1.00 (threadTitleCloud b0.1.0)';
$urlYahooAPI = 'http://api.jlp.yahoo.co.jp/MAService/V1/parse';
$title = 'threadTitleCloud';
$poweredby = 'threadTitleCloud b0.1.0';
$description = 'boardName '.date('r');
/* 関数 */
//mb_str_replace - fetus
//http://fetus.k-hsu.net/document/programming/php/mb_str_replace.html
/**
* マルチバイト対応 str_replace
*
* @version     Release 2
* @author      HiNa (hina@bouhime.com)
* @copyright   Copyright (C) 2006-2007 by HiNa(hina@bouhime.com).
*/
/* 省略 */
/* メインルーチン */
//subject.txtを取ってくる
$urlSubjectTxt = $target.'subject.txt';
$cli = new HTTP_Client();
$cli -> setDefaultHeader(
  array(
    'user-agent' => $ua
  )
);
try {
  $cli -> get($urlSubjectTxt);
} catch (PEAR_Error $e){
  header("$_SERVER[SERVER_PROTOCOL] 400 Bad Request");
  header('Content-Type: text/plain;charset=UTF-8');
  exit($e->getMessage());
}
//Yahoo!APIが食べやすいように整形
$response = $cli -> currentResponse();
$html = $response['body'];
mb_convert_variables('UTF-8','SJIS-win',&$html);
$arraySubjectTxt = explode("\n", $html);
$pattern = '`^.*<>(.*)[ ]\(.*\)$`';
$text = array();
$i = $j = 0;
foreach($arraySubjectTxt as $value){
  $i++;
  if($i % 400 === 0){$j++;}
  preg_match($pattern, $value, $match);
  $temp = mb_convert_kana($match[1], 'KVas', 'UTF-8');
  $temp =  mb_str_replace(' ', '', $temp, 'UTF-8').' ';
  $temp = preg_replace('`[0-9]`u','',$temp);
  $text[$j] .= $temp;
}
//Yahoo!APIをコール
$call = new HTTP_Client();
foreach($text as $value){
 
  $param = array(
      'appid'    => $applicationID,
      'sentence' => $value,
      'results'  => 'uniq',
      'filter'   => '9'
    );
 
 
  try {
    $call -> post($urlYahooAPI, $param);
  } catch (PEAR_Error $e){
    header("$_SERVER[SERVER_PROTOCOL] 400 Bad Request");
    header('Content-Type: text/plain;charset=UTF-8');
    exit($e->getMessage());
  }
 
  $response = $call -> currentResponse();
  $xml[] = new SimpleXMLElement($response['body']);
}
$arrayWord = array();
foreach($xml as $value){
  foreach ($value->uniq_result->word_list->word as $wd) {
    if(array_key_exists((string)$wd->surface, $arrayWord)){
      $arrayWord[(string)$wd->surface] += (int)$wd->count;
      continue 1;
    }
    $arrayWord[(string)$wd->surface] = (int)$wd->count;
  }
}
//HTML_TagCloudにタグと回数を食べさせる
$max_count = false;
$cloud = new HTML_TagCloud();
foreach ($arrayWord as $key => $value) {
    if ($max_count === false) {
        $max_count = $value;
    }
    $cloud->addElement($key, null, $value * (10000 / $max_count));
}
echo $cloud->buildHTML();

動作確認では狼板を対象にしました。あと上のコードに加えてCache/Lite使ってます。

追記(2007.06.26 08:13)
コードをこっそり修正。foreachの中で間違えてbreakしてたのをcontinueに。

自分で作ったAPIを自分で使うのに苦戦中

iwnidAPIで引っ張り出せるのはShift_JISのXML。これをSimpleXMLに掛けるとタイトルがUTF-8に変換されて返ってきちゃう。simpleXMLがShift_JIS読めないからなんだろうけど、なんとかShift_JISのままで返るようにしたい。2バイト文字を<![CDATA[ ]]>の中に入れておけば無理やりなんとかならないものかな。

多分setなんとかみたいな設定系のものをsimplexml_load_fileの前に入れればいいんだろうけど、設定が見つけられず。苦戦中。

iwnidAPI スレッドデータをXMLで返す

概要

iwnidで取得した中から指定したスレッドのデータをXML形式で返します。

リクエストの形式

http://example.com/boardName/xml に対して以下のパラメータをGETリクエストで送信することで、XML形式でスレッドのデータを取得することが出来ます。

all
全てのスレッドデータを取得します。all以外のパラメータはname=valueで指定しますが、allallのみで指定します。また、allは他にパラメータがある場合、省略が可能です。

live
liveなスレッド限定でスレッドデータを取得するならtrue、liveではないスレッド限定で取得するならfalseを指定します。何も指定しなければ両方を取得します。

start
スレッドデータの選択範囲の開始日時を八桁の整数で指定します。はじめの四桁が年、次の二桁が月、最後の二桁が日になります。それぞれ0以上の整数である程度柔軟に解釈します。
 ex.20070331,20070400,20061531は全て2007年3月31日を指します。

end
スレッドデータの選択範囲の終了日時を八桁の整数で指定します。はじめの四桁が年、次の二桁が月、最後の二桁が日になります。それぞれ0以上の整数である程度柔軟に解釈します。
ex.20070331,20070400,20061531は全て2007年3月31日を指します。

time
開始日時・終了日時をスレ立て時刻で得るならstart、最終確認時刻で得るならconfirmを指定します。指定がない場合のデフォルトはstartです。

limit
一度に取得したいスレッドデータ件数を整数で指定します。指定がない場合は全件取得です。

offset
オフセット値を整数で指定します。オフセット値の指定にはlimitの指定が必須です。オフセット指定がない場合のデフォルトは0です。

order
スレッドデータを昇順ソートする場合はasc、降順ソートする場合はdescを指定します。指定がない場合のデフォルトはascです。

mode
自動収集データのみを取得するにはauto、手動データのみを取得するにはhandを指定します。指定がない場合は両方を取得します。

XMLデータの構造

返り値として得られるXMLデータは以下のような構造になっています。

mainURL
基準ページのURLです。

mainTitle
基準ページのタイトルです。

editor
編集著作権者です。

subject
subject.txtの相対位置です。

lastModTime
最終巡回時刻です。

allThreadCount
保持しているスレッド総数です。

threads
個々のスレッドデータをthread要素に持ちます。

threadの子要素

startTime
RFC2822形式のスレ立て時刻です。

lastConfirmTime
RFC2822形式のスレッド最終確認時刻です。

mode
自動収集の場合auto、手動の場合handです。

title
スレッドタイトルです。

live
liveなスレッドの場合true、livrではないスレッドの場合falseです。

resCount
レス数です。

readURL
スレッドを読む場合の相対位置です。

datURL
datの相対位置です。

originalURL
元スレッドのURLです。

iwnidAPI使用条件

個々のスレッドタイトル及びそれに付随する情報の著作権は2ちゃんねる及びスレ立て人に帰属します。iwnidが収集したスレッドの編集著作権は私(sanemat)に帰属します。

sanematに帰属する著作権の範囲では、クリエイティブ・コモンズ 表示 2.1 日本 ライセンス Creative Commons License の下でライセンスします。最小限の表示は’Powered by iwnid, sanemat’のテキスト、それ以上は好きにしてください。また、2ちゃんねるのデータの利用に関しては
2ちゃんねる
http://www.2ch.net/
を参照してください。

iwnidAPI使用制限

現在、呼び出し回数及び転送量に制限はありません。試行錯誤歓迎です。ただしこれは将来にわたって無制限を保障するものではありません。特に、呼び出し側で適度にキャッシュしてもらえると助かります。

リクエストの例

liveな最新1スレを取得する
http://example.com/boardName/xml?limit=1&order=desc&live=true

返り値として得られるデータは以下のようになります。

<?xml version="1.0" encoding="Shift_JIS" ?>
<iwnid>
  <mainURL>http://example.com/boardName/</mainURL>
  <mainTitle>@iwnid</mainTitle>
  <editor>sanemat</editor>
  <subject>subject.txt</subject>
  <lastModTime>Sun, 17 Jun 2007 01:14:22 +0900</lastModTime>
  <allThreadCount>1252</allThreadCount>
  <threads>
    <thread>
      <startTime>Sun, 17 Jun 2007 00:42:23 +0900</startTime>
      <lastConfirmTime>Sun, 17 Jun 2007 01:14:15 +0900</lastConfirmTime>
      <mode>auto</mode>
      <title><![CDATA[【野球】ドン底阪神の景気づけプラン そらそうよドリンク作る ]]></title>
      <live>true</live>
      <resCount>2</resCount>
      <readURL>nonyu.php/1182008543/</readURL>
      <datURL>dat/1182008543.dat</datURL>
      <originalURL>http://news21.2ch.net/test/read.cgi/mnewsplus/1182008543/</originalURL>
    </thread>
  </threads>
</iwnid>

読んだだけでは何がなんだかよくわからないと思います。ですので、xml?allとかxml?live=true&order=desc&limit=10とかxml?start=20070501とかそんな感じでいろいろやってみてください。

iwnid b0.2.4

[新機能] API機能(REST)をつけました。

iwnidでログ収集をしていると、index.htmlはどんどん肥大化していきます。この点、表示部分をカスタマイズしてpager等で分割表示することは現状でも可能です。でもどうせなら、ということでindex.htmlに加えてデータをSQLiteに書き出し、さらにそれをAPI(REST)で呼び出すことが出来るようにしました。

GETでリクエストするとデータがXMLで帰ってくる、というあれです。これにより誰でもデータを引っ張り出して好きなように組み立てることが出来ます。

携帯用の省スペース版、直近10スレッド、スレッド表示を月別に、新着をRSSフィードとして配信、週毎のスレッド数統計、今liveなスレッド一覧、スレ立て時間帯でスレッドを分類、などなどアイデア次第でパターンは無限大です。逆に言えば必要な人は自分でデータをカスタマイズしてね、ということです。

詳しい使い方・私のところでの使用条件は改めて別エントリに書き出します。