オオカミ少年から学ぶメディアとの付き合い方

イソップ童話の「オオカミ少年」は、嘘をついた少年が狼に食われるという結末が有名ですが、もともとそんな結末はなくて、「村の羊はすっかり食べられてしまったとさ」というのが結末だったんだそうです。

日本ではイソップの話であるとして、狼に食べられるのは羊ではなく「羊飼いの少年」とする寓話がいくつも存在する。
(嘘をつく子供 – Wikipedia)

誰かが作り替えた話が、「そっちのほうがわかりやすいから」ということでどんどん広まっていって元の話を駆逐してしまう展開は、パクツイとかバイラルメディアでよく見られる現象ですね。

ところで、「村の羊はすっかり食べられてしまったとさ」という結論が元だとすると、これってどういう教訓だったんだろう、というのがfacebookで話題になりました。

食べられたのが少年じゃなくて村の羊ということは、村の人々の行動に問題があったということになります。ウソだかホントだかわからない話を鵜呑みにして、いきなり全員で武器を持って大騒ぎしちゃったことが問題なんじゃないでしょうか。
村人は、少年の話をきいた時点で、とりあえず武器を持った人を索敵に出して、他の人は冷静に待機しておくべきだったのです。
そういう対応なら、嘘だと判明しても「コラ!」って叱れば済む程度の話だっただろうし、対応コストが低いから、二回目以降も同じ行動を取り続けることが出来て、本当に狼が来た時には全力で事にあたることが出来たはずです。

センセーショナルな情報を見たからといって、真偽を確かめずに大騒ぎしてはいけない。

最近はバイラルメディアとかの流行りで、「わー!」「大変!」と言いたくなるようなタイトルの付いた記事があふれています。いちいちまともに受け取って騒いでいたら、体も心も疲れてしまいますよね。
本当に「これは大変だ」と思うのなら自分で真偽を確かめてから話題にすべきだし、そうでないのなら「ふーん」と言いながら判断を保留するくらいの態度がいいんじゃないでしょうか。オオカミ少年の話題を聞いてそんなことを思いました。

RubyHiroba2014のLTについて

RubyHirobaのLTについていくつかお話できることを。

結果的につまらないプレゼンで時間をとってしまって、さらに運営に余計な対応をさせるはめになってしまったことはホント申し訳ないと思っております。

今回話題が公になったので、引き続き議論していただくための参考資料として、僕の側から見て何が起きていたのかを説明させていただきます。

前日まで

 RubyMLのメールでLT募集のことを知りました。

[ruby-list:49953] 9/21(日) RubyHiroba2014の参加受付中です

 これは面白いんじゃないかな、と思って、今回のようなLTをやろうと思っている旨を当人に相談した所、OKが出たので、LTthon | RubyHiroba 2014を見て、応募フォームからタイトル書いて申し込みました。この時、アンチハラスメントポリシーにはぜんぜん気付かなかったです。わりとギリギリの話題をやろうとしている自覚はあったので、気づいていたら止めてたと思います。

 LT申し込みフォームにタイトル記入欄があったから、内容が明快にわかるように「社長が美人で何がわるい」というタイトルで申し込みました。ダメならここで止めてくれるだろうと思ってました。
 結果的には、工数がないからノーチェックで通ってしまったのだそうです。ボランティアだからそうなることは仕方ないと思うのですけど、この時点でそんな可能性はまったく思いつかなくて、当日の案内メールが来たことで「あ、OKなんだな」と理解してしまいました。

当日

 一人目の人が欠席で、結果的にぼくがトップで発表することになりました。

 発表が*終わってから* 「ところでRubyHirobaにはアンチハラスメントポリシーというのがあります」といわれて、ドキっと思わなくもなかったのですが、その後「PHPをDisったりするのは禁止です」というから、「あ、これは場を盛り上げるためにぼくのネタを引き継いで冗談を言っているのだな」と理解しました。

 その後も、Tシャツ取りに行った時とか、「ここで食事しても大丈夫ですか?」ってスタッフの方に確認に行った時とか、どこかのタイミングで直接言っていただけたら、記事の公開を見合わせるとかもありえたのですけど、上述の通り冗談をいわれていると思っていたので、ぎりぎりオッケイなおもしろいプレゼンをしたつもりでいました。

 時間があればもう一個枠をいただいてRails+GoogleAnalysticsの話もやろうと思っていたのですけど、前日から体調不良気味だったし、午後になって枠がいっぱいになっていたので、無理せず帰宅しました。ひょっとしたら運営の方は、終わってからお話をしたかったのかもしれないですね。

月曜日

 土曜日のRuby関西同窓会で、LTと同様の話をしてスカウトした方がオフィスに来てくれたので、僕としては成功したつもりになってました。
そこで、資料に出ている当人にプレゼンを見せて、「公開してもいい?」と聞いたところ、「いいよ」とのことだったので、ノリノリで資料も公開しました。

半日後くらいにRubyHiroba運営の方からメールを頂いて、ここで初めて事態を悟りました。
指示に従って資料は非公開にして、連絡を待っていたのですが、今日になって、「RubyHirobaの
アンチハラスメントポリシーに明確に抵触するものではない、という結論になりました」というメールを頂きました。WEBサイトで「残念ながら、ジェンダーや性的な表現について、アンチハラスメントポリシーに抵触する発表がありました」と書かれていることとの整合性については、僕にはよくわからないです。

LTthonで行っていただいたプレゼンですが、運営で協議したところ、RubyHirobaの
アンチハラスメントポリシーに明確に抵触するものではない、という結論になりました。
曖昧な言い方で大変申し訳ないのですが、「あのようなポリシーを掲げているイベント
での発表としてはあまり好ましくない。しかし、どこがダメで誰に対してのハラスメント
であるかを明確にすることが難しい」という判断です。

ただ、ポリシーに抵触するのかしないのかはさして重要ではなくて、くだらないLTのために運営に余計な手間を取らせてしまったことはほんと申し訳ないし、調子のってたなーと思っております。以後気をつけます。

respond.jsが効かないもうひとつの理由

modern.ieでIE8の動作確認していて、昨日まで動いていたはずのデザインが突如崩壊した!リリース直前なのに!respond.jsが効いてない!?なんで?
コードを戻してみたり、「respond.js 効かない」でググってみても直らない。迫るリリース時刻!電車の動き出す音!
って時は、そのVirtualMachineが外部ネットワークに接続できるかどうか確認することをおすすめします。CDN上のrespond.jsはネットワークにつながってないと当然動きません。
ホストマシンのrailsでテストしていると意外と気づかないので、パニックになった時は一度ご確認を。
・・・あー。びっくりした。

[Titanium]電源コンパス

device-2012-10-10-083524.png

Mashup Awards 8 (#MA8)にモバイラーズオアシス電源検索APIを提供しているので、API提供企業(!)として
#MA8 東京CaravanVol.03に行ってきました。

APIの活用サンプルということで、つねに最寄りの電源スポットのほうを指し示すコンパスアプリというのをつくってみたので、ソースコードを公開します。

[Titanium] dpとpixelの変換

Androidアプリ with TitaniumMobileに挑戦中。iPhoneだと動くコードが動かない理不尽さを見ていると、HTMLを書いてIE6で見ると崩れまくっていたあの頃を思い出しますね。

さて、そんなandroidでは、解像度の異なる端末に対応するため、数値はdip指定するのが原則です。

  • New Defaults for Android Layouts in 1.7 « Appcelerator Developer Center
  • Y.A.M の 雑記帳: Android multi screen 対応
  • ところが、view.animateとか、scrollView.setContentOffsetのように構造体をとる関数はたいていdp表記に未対応で、pixel単位で指定してあげる必要があります。変換関数が必要!

    このあたりのことはこの記事で解説されていて、

    TitaniumMobileのハマりポイントとお作法メモ at HouseTect, JavaScriptな情報をあなたに

    dpとpixclの変換関数なんかも提案されているのですけど、用途と合わなかったので、俺バージョンを作りました。

    元コードとの違いは

    • View.widthなどで取得できる ‘100dp’などの記述をそのまま渡せるようにした
    • 同様に、view.widthにそのまま渡すことを意識して、dpは’100dp’みたいな文字列で返すようにした

    というあたりです。

    じゃらんで見た宿の情報が漏洩しまくっている

    twitterのまとめで有名なtogetterのこの辺に、じゃらんとか楽天の広告が出てきますよね。
    WS000.JPG
    右上の(i)をクリック。


    「あなたのブラウザでは、jalanjpのWebサイトで以下の商品をご覧になられています。」

    ご覧になられています、じゃねー! どこだか分からない広告会社が何でそんなことを知っている!

    これは、第三者クッキーと呼ばれる技術をつかったものです。つまり、CRITEOという広告会社が、Togetterにも じゃらんにも広告を配信しているので、じゃらんの閲覧履歴を元にTogetterに広告を出すことが出来ているわけですね。

    じゃらんのプライバシーポリシーを見ると、たしかにこの広告が使われていることが説明されています。

    この話題、気づくきっかけになったのはこのブログの記事でした。

    バナー広告の下に「楽天」とあるので楽天の広告と思ったのですが,右クリックすると「Powerd by Criteo」とあるのでCriteoによる行動ターゲティング広告であることがわかりました.
    (CRITEOの広告が楽天でチェックした商品を表示している件: 何の変哲もない福岡生活…)

    楽天のサイトにも、たしかにCRITEOの広告が使われていることが明示されています。

    二匹いたらもっといると思え。

    これは気持ち悪い。つまりCRITEO社は、僕が楽天で鳥かごを買った買おうとしたことだけじゃなく、All Aboutでジャムのレシピを調べたことも、時事通信で昨日の阪神タイガースの勝ち負けを見たことも全部知っているわけです。
    ヨメと今度行こうかと思っていた旅行の行き先まで!

    これは気持ち悪すぎる!断固抗議だ!

    ふと振り返ると

    ところで、自分の運営しているモバイラーズオアシス-街の電源検索サイトでは、Googleアドセンスを使って広告を表示しています。あと、このブログにも出ているはずです。
    このGoogleアドセンスも、行動ターゲティング広告をやっています。

    Google Japan Blog: 新しい広告のプログラムのテストについて

    つまり、モバイラーズオアシスだって、Googleを通して上記の気持ち悪い状況をつくりだす一翼を担っていることになりますね。えらそうにリクルートさんを批判できる立場にはないわけです。

    かといって、今モバイラーズオアシスからアドセンス広告を外すと、運営が干上がってしまいます。ううむ。

    言い訳かもしれないのですけど、GoogleとCriteo社では気持ち悪さが全然違うと思うのは、僕の勝手でしょうか?
    Googleであれば、ストリートビューの件を初めとして、これまでいろいろな事件が起こって、それに対してどんな対応をしてきたか一通り見てきています。あるいは、及川さんを初めとして、中の人も何人も知っています。そのGoogleなら、まあそんなに無茶はしないだろう、という気がするのです。
    我が身かわいさ故の偏見かなぁ。

    第三者クッキーを拒否する方法

    罪滅ぼしになるかどうかは分かりませんけど、最後に、この手の追跡を回避するために、第三者クッキーを拒否する方法を紹介しておきます。
    お使いのブラウザによりますけど、

    という感じです。safariは最初から拒否する設定になっているみたいですね。

    気持ち悪いことに気づいたので広告を全廃しました!とか、リクルートにたいする抗議運動を開始します!とかかっこいい記事じゃなくて申し訳ないのですけど、とりあえず、これを見て気持ち悪いと感じた人が増えるだけでも、世の中なんか変わるのではないかな、と期待を込めて。

    2012-05-13 10:25:20訂正
    楽天で「買った」ものの情報は流れていないみたいですね。その前の閲覧履歴が渡っているので、これがいいかな、あれがいいかな、と思っている段階の情報が流れているだけのようです。

    [プラグイン不要]Chromeのショートカットキーカスタマイズ

    Macでどうしても馴染めないショートカットキーが幾つかあります。
    一番困るのがGoogleChromeのMeta+h。Windows版ChromeのCtrl+hは履歴を表示なのですが、Mac版ChromeでMeta+hを押すとウインドウが隠れてしまいます。
    ブラウズ中に手軽に押してしまうものなので、その瞬間ウインドウが消えてなくなるのはかなり戸惑います。UIが良くできているので、隠れたことはわかるのですが、そのたびに作業が中断するのは辛いです。

    で、色々試した末、プラグインも何も入れずにMeta+hをヒストリーに割り当てることに成功したのでご紹介。

    keyboard2.png
    これだけ。OSのシステム環境設定→キーボード→キーボードショートカット→アプリケーション のところで+ボタンを押して、アプリケーション用のショートカットキーを割り当てればOK。
    「メニュータイトル」のところは、素直にメニューにあるテキストをそのまま入れればいいみたい。
    menu-1.png

    Windows7で音声合成(Text to speech)

    ヨメから、「Windowsで中国語テキストの読み上げをできないか」とのご要望をいただいたので、「昔そんなことやったなぁ」と思いながら調べた内容まとめ。普通の読者の方は日本語読み上げだと思いますけど、だいたい同じ手順で出来ます。

    手順の概要

     Windowsには、音声合成(SAPI)という機能が搭載されています。これをつかうと、Excelや、Vectorでダウンロードできる各種SAPI対応アプリケーションでテキストを読み上げさせることができます。
     ※なんでWordじゃないのかは謎。
     ただし、Windows7のProfessional程度では英語のランタイムしか搭載されていないので、日本語や中国語を読み上げるためには別途ランタイムが必要。このランタイムがどうも無料で手にはいらないっぽい。UltimateのDVDに入っているという噂。
     一方、SAPIとほとんど同じ機能なんだけど微妙に異なるぽいSpeech Platform Server Runtimeというのがあって、これは各言語ランタイムが無料で配布されている。そして、Speech Platform Server RuntimeをSAPIとして認識させちゃう裏技があるので、これを使えばExcelでテキストを読み上げさせることが出来る!

    Microsoft Speech Platformとランタイムのインストール

    Download: Microsoft Speech Platform – Runtime (Version 11) – Microsoft Download Center – Download Detailsから、自分の環境にあったSpeechPlatformRuntime.msiをダウンロードしてインストール。Windows7 64bit版の人はx64_SpeechPlatformRuntime、32bit版の人はx86_SpeechPlatformRuntime。「わかんない」というような人はきっと32bit版です。
    Microsoft Speech Platformのダウンロード

    Download: Microsoft Speech Platform – Runtime Languages (Version 11) – Microsoft Download Center – Download Details
    から、必要な言語のTTSランタイムをインストール
     ※「zh」とか「ja」で検索すれば見つかる。
     ※「MSSpeech_SR」で始まるのは音声認識用ランタイム、「MSSpeech_TTS_」で始まるのが音声合成用ランタイム。今回必要なのは音声合成用ランタイムなので、間違えないように注意。
    音声合成用ランタイムのダウンロード

    Speech Platform Server RuntimeをSAPIとして認識させる

    スタート > すべてのプログラム > アクセサリ > コマンドプロンプトを右クリックして、「管理者として実行」。権限昇格のために管理者パスワードを要求させるので入力。開いたコマンドプロンプトで、下記を実行する。

    reg COPY “HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech Server\v11.0\Voices\Tokens” HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\Voices\Tokens /s /f

     ※または、レジストリエディタを立ち上げて、図のようにコピーしてもよい。
    レジストリをコピー

    音声合成(SAPI)の言語を変更

     「コントロール パネル > すべてのコントロール パネル項目 > 音声認識 > 音声合成」を開いて「音声の選択」で適切な言語を選ぶ。
     ※ここまでの手順が失敗しているとMicrosoftAnnaしか出てこない。その時はおやつでも食べて考え直す。

    Excelで読み上げ

     読み上げコマンドが表示されない – Word – Office.comを参考に、読み上げコマンドを表示させる。
    Excelのセルに適当な文章を入力して、読み上げボタンを押すと読み上げられる。
    ボタンはここ

    謝辞

    キーとなったレジストリの変更のアイデアを初め、だいたいのアイデアは下記が出所です。このブログ記事は、下記を元に、最近の環境で実行できるようにまとめ直しました。

    日本語や中国語等の音声合成(SAPI TTS)を無料で使う方法(Speech Platform Server Runtimeを裏技で SAPI 5.1として動作させる方法)

    [Titanium]CFBundleVersion must be no longer than 18 charactoers

    個人的なポリシーで、アプリケーションのバージョン番号は、1.0, 1.1, 2.0とかじゃなくて、20110923, 20110925…っていうふうに年月日でつけることにしています。
    どこでマイナーバージョンを上げてどこでメジャーバージョンを上げるかとか悩んでも仕方ないし、ユーザーさんにとってはどっちが新しいかさえ分かればOKなんだから、無理に数字に丸める理由はないよね。

    というような経緯で俺ポリシーを採用していたら、えらい目にあいました。

    理由は知らないんだけど、Titanium MobileはInfo.plistに書いてあるBundleVersionの後ろに、タイムスタンプをくっつけてしまいます。例えばInfo.plistに「1.0」と書いた場合、実際のビルドに使用されるCFBundleVersionは「1.0.1316956026」という具合に。
    普通の人はバージョンなんてせいぜい5文字だから、
    日付時刻をつけても弊害はなかったのだけれど、年月日方式だと、「20110925.1316956026」となってしまいます。実はバージョン番号には18桁までという制限があるので、これだとCode SignをするときのVelificationでエラーになってしまうのです。

    厄介なことに、このアプリの旧バージョンはXCodeで開発していたので、年月日方式ですでにリリースしてしまっています。
    しょうがないので、「3.0」にしてみたんだけど、これも「The key CFBundleVersion in the Info.plist file must contain a higher version than that of the previously uploaded version.」ということでどうにもならなくて泣きそうに。Adhoc Buildまで無事通って、最後の最後に発覚するので心理的ダメージがでかいです。

    そういうわけで、このTitanium Mobileの余計な処理を止める方法。
    builder.py ( /Library/Application Support/Titanium/mobilesdk/osx/#{TITANIUM_SDK_VERSION}/iphone/ )をひらいて、この辺をコメントアウトする。

    僕から見たら余計な処理だったけど、書いてあるからには必要な理由があったのだろうから、普通の人はまねしない方がいいと思う。
    バージョン番号が長くなって困った方だけ、自己責任でどうぞ。

    おまけ:Distribution Buildのハマリどころ

     iPhoneアプリの開発は、エミュレーターで動いて、AdHocビルドも動いているのに、最後の最後でリリースできない!って苦労することが多い気がする。
    自分が今回はまった点と解決策。

    • Distribution Buildした後、Validateするとすっごい長いエラーダイアログが出る
      →キャプチャしておくのを忘れたんだけど、ほぼ画面いっぱいのエラーダイアログ。長すぎて結局何がエラーなのか全然分からなかったんだけど、ダイアログの上でcommand+Cを押すと全文がコピーできるので、エディタに貼り付ければ内容を見ることが出来る。
    • 「Icon specified in the Info.plist not found under the top level app wrapper: icon.png Unable to verify icon dimensions, no icon found」
      →可能性1:info.plistに書いてあるアイコンファイルと実際においてあるアイコンファイルの名前が違う。大文字小文字にも注意。
      →可能性2:アイコンファイルが8bit pngになっている。24bit pngじゃないとダメらしいです。
    • 「a sealed resource is missing or invalid」
      →Resourcesに余計なファイルがあると起きるらしい。.gitとか.gitignoreとかそのへんをひと通り削除したら出なくなった
    • 「CFBundleVersion must be no longer than 18 charactoers」
      →上に書いたとおり。「バージョン番号は18文字以下でないといけません」。

    CodeSignとかMobileProvisioningになってしまえば、もはやTitaniumMobileであることはあまり関係が無いので、「エラー文字列 iPhone」でググるのが王道のような気がしました。

    Re: Twitter oAuth Implementation for Titanium Mobile

    TitaniumMobileで簡単にTwitterのOAuthを通すことが出来る(というふれこみの)oauth-adapterというのがあって。
    実際この記事通りにするとあっという間にtwitterにつぶやくことが出来る。

    Twitter oAuth Implementation for Titanium Mobile « Appcelerator Developer Center

    問題なのはその先で。このコード、どうやらPOSTしかできないので、updateはできるけどhome_timelineがとれないみたい。
    @chris4403がoauth-adapterを直してはてなAPIで動くようにしたtitanium-hatena-oauth-sampleを参考に、TwitterのGETメソッドも使えるoauth-adapterに直してみた。

    mogya/oauth-adapter – GitHub

    sendメソッドだけ、元のコードと引数が違います。

    • params.url APIのエンドポイント
    • params.parameters APIに渡す引数
    • params.title 成功/失敗の時に出すダイアログのタイトル
    • params.successMessage 成功した時に出すメッセージ
    • params.errorMessage エラーだった時に出すメッセージ
    • params.method ‘POST’/’GET’

    つぶやく


    //initialization
    Ti.include('lib/oauth_adapter.js');
    var oAuthAdapter = new OAuthAdapter(
    'YOUR_CONSUMER_SECRET_HERE',
    'YOUR CONSUMER KEY HERE',
    'HMAC-SHA1'
    );
    // load the access token for the service (if previously saved)
    oAuthAdapter.loadAccessToken('twitter');

    //OAuth if need.
    if (oAuthAdapter.isAuthorized() == false)
    {
    var receivePin = function() {
    oAuthAdapter.getAccessToken('https://api.twitter.com/oauth/access_token');
    oAuthAdapter.saveAccessToken('twitter');
    };
    // show the authorization UI and call back the receive PIN function
    oAuthAdapter.showAuthorizeUI('https://api.twitter.com/oauth/authorize?' +
    oAuthAdapter.getRequestToken('https://api.twitter.com/oauth/request_token'), receivePin);
    }

    //TWEET
    oAuthAdapter.send({
    url:'https://api.twitter.com/1/statuses/update.json',
    parameters:[
    ['status', '@mogyatest test from tmtwit. '+Math.random()]
    ],
    method:'POST',
    title:'Twitter',
    successMessage:'tweeted',
    errorMessage:'tweet failed'
    });

    GET TIMELINE


    //same code for initialization and authrization.
    //get timeline
    var responce = oAuthAdapter.send({
    url:'https://api.twitter.com/1/statuses/home_timeline.json',
    parameters:[
    ],
    method:'GET',
    title:'Twitter',
    successMessage:'success',
    errorMessage:'failed'
    });

    if (responce){
    Ti.API.debug(responce);
    responce = JSON.parse(responce);
    //Enjoy with this responce:-)
    }