Linux上で動くSkype用のbotを作る方法
はじめに
以前、知人のやっているBeProudという会社を手伝ったのですが、BeProudでは、エンジニアの主要なコミュニケーション手段としてSkypeが使われていました。当時、趣味でたまたまSkypeのAPIについて調べていたので、悪戯っ気を出して、開発環境に即席でSkype APIを使ったbotを設置してみたところ、思いのほか好評を博し、いまやインフラと言っても過言ではない存在 *1 *2と化したようです。
まあそんな状況を眺めつつ、自宅のサーバにSkype botを設置して、お気に入りのSkypeチャットにもbotを加えてみたところ、これも結構好評だったので、興味ありそうな人向けに作成方法をまとめることにします。
Skype Public API
Skype Public APIとは、Skypeを外部からコントロールするためのインターフェイスです。
Skype Public APIをLinux上で簡単に利用するには、スクリプト言語バインディングであるSkype4PyかPHP Skype API wrapperがおすすめですが、ここでは準公式な位置付けでAPIが充実しているSkype4Pyを使います。
Skype4Pyのインストール
Ubuntuであれば、multiverseに「python-skype」というパッケージ名で収録されているので、aptitude一発でインストールできます。OpenSUSEにも「python-Skype4Py」というパッケージがあるようです。
他のディストリビューションでも、setuptools経由で簡単にインストールできます。
$ easy_install Skype4Py
Skype4Pyを試す
最初は肩ならしにPythonのインタラクティブモードでSkype4Pyを使ってみることにします。
手元にまずXが立ち上がっていてSkypeが使える環境を用意してください。また、必要に応じてbot用のアカウントも取得しておきます。
Skypeを起動し、ユーザ名パスワードを入力して、サインインしたところで、ターミナルを開き、Pythonを立ち上げます。
Python 2.6.5 (r265:79063, Apr 16 2010, 13:57:41) [GCC 4.4.3] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>>
Skype4Pyをインポートして、Skypeオブジェクトを生成し、Attach()メソッドを呼びます。
>>> import Skype4Py
>>> skype = Skype4Py.Skype()
>>> skype.Attach()
最初にこの操作を行うとき、SkypeはAPIを利用するアプリケーションを信用してもよいかを確認するプロンプトを出し、APIの呼び出し側はこのプロンプトに対して選択が行われるまでブロックします。ここでは、もちろん[はい]を選択します。
選択が終わると、再びPythonのインタラクティブモードに処理が戻ります。
手始めにコンタクト一覧を取得し、その名前を表示してみましょう。
>>> skype.Friends (<Skype4Py.user.IUser object at 0x7f6cb3876890 with Handle=u'echo123'>, <Skype4Py.user.IUser object at 0x7f6cb38766d0 with Handle=u'moriyoshi_koizumi_2nd'>) >>> for user in skype.Friends: print user.Handle, user.FullName ... echo123 Skype Test Call moriyoshi_koizumi_2nd moriyoshi
次に、Skype Test User (echo123) に向かって何かメッセージを送信してみましょう。
>>> chat = skype.CreateChatWith("echo123") >>> chat.SendMessage("test") <Skype4Py.chat.IChatMessage object at 0x7f67bde5c690 with Id=82> >>> chat.SendMessage(u"テスト") <Skype4Py.chat.IChatMessage object at 0x7f67bde5c950 with Id=85>
チャットウィンドウを開くと、echo123に対してメッセージが送られ、同じ内容がecho123によってエコーバックされていることが確認できると思います。
では、メッセージ受信などのイベントが発生したときに特定の処理を行うイベントハンドラを試してみます。
>>> def handler(msg, event): ... print event ... >>> skype.OnMessageStatus = handler >>> chat.SendMessage("???") <Skype4Py.chat.IChatMessage object at 0x7f67bde5c890 with Id=125> >>> SENDING SENT RECEIVED >>> def handler(msg, event): ... if event == u"RECEIVED": ... print msg.Body ... >>> skype.OnMessageStatus = handler >>> chat.SendMessage(u"\(^o^)/") <Skype4Py.chat.IChatMessage object at 0x7f67bc484190 with Id=138> >>> tof-bot 2010.09.26 02:33:10 \(^o^)/
以上を踏まえて、「やっぱり」というメッセージを受信すると「猫が好き」と返すbotを書いてみましょう。
# encoding: utf-8 import Skype4Py import time def handler(msg, event): if event == u"RECEIVED": if msg.Body == u"やっぱり": msg.Chat.SendMessage(u"猫が好き") def main(): skype = Skype4Py.Skype() skype.OnMessageStatus = handler skype.Attach() # イベントハンドラは別スレッドにて実行されるので、 # 本スレッドではひたすらsleepしてスクリプトが終了しないようにしておく。 while True: time.sleep(1) if __name__ == "__main__": main()
これを
$ python yappari.py
のように起動して、別マシンから「やっぱり」を送信してみましょう。次のスクリーンショットのようになれば成功です。
例がすみませんね昭和で。
と、まあ、どうでしょう、たったこれだけのAPIを覚えれば、botが作れてしまうということがわかったのではないかと思います。
Skypeをデーモン化する
と、これまでSkypeが予め立ち上げられたGUI環境上でSkype Public APIを利用する方法を説明しましたが、これではサーバに設置するのがやや面倒です。非GUI環境でSkypeを起動し、それを利用してbotを書くことはできないものでしょうか。そのような時に威力を発揮するのがXvfbです。Xvfbとは、その名前のvfbがvirtual framebufferから由来していることからもわかるように、フレームバッファ (画面への出力内容を蓄えておくメモリ) として、ビデオカードを使うのではなく、RAM上に確保した仮想フレームバッファを使うというXの実装です。これを使うことで、CUI環境下でXサーバを簡便に扱うことができます。
Xvfbの起動方法は簡単で、Xのディスプレイ番号を引数に与えるだけです。
$ Xvfb :20
デフォルトではスクリーンサイズが1280x1024でピクセルの深さが24となります。これを変更するときは
$ Xvfb :20 -screen 0 800x600x8
のように-screen
オプションを指定します。一般に、画面サイズが小さければ小さいほど、ピクセルの深さが小さければ小さいほど、必要なフレームバッファのサイズが小さくなり、省メモリとなります。Skypeはかろうじて画面サイズ800x600、ピクセルの深さ8で動作させることができます。
Xvfb上で動くアプリケーションを外部から操作するには、VNCを利用します。VNCサーバの実装はいろいろありますが、コマンドラインから手軽に使える、各種ディストリビューションでパッケージが用意されているという点でx11vncをおすすめします。
x11vnc の利用方法も簡単で、
$ x11vnc -display :20
のようにXのディスプレイ番号を与えるだけです。デフォルトの接続ポート番号は5900番ですが、これを変更するには-rfbport
オプションを指定します。
と、能書きはここまでにして、CentOSなどRedHat系のディストリビューションやDebian系のディストリビューションで動くinitスクリプトをとりあえず貼ります。sudoを使っているので、デフォルトでインストールされていない環境にはあらかじめインストールしておいてください。
このスクリプトはDAEMON_USERで指定されたユーザでXvfbおよびskypeを起動または停止します。/etc/init.d以下にinitスクリプトとして設置して以下のように使います。
- 起動
$ /etc/init.d/launch-skype.sh start
- 停止
$ /etc/init.d/launch-skype.sh stop
initスクリプトがOS起動時に自動的に呼び出されるように設定する方法は、各ディストリビューションのドキュメントを参照してください。
起動ログは/var/log/skype以下に、xauth cookieは/var/run/skype以下に保存するので、あらかじめこれらのディレクトリをDAEMON_USERで読み書きできるように作成しておく必要があります。
Skypeでサインインするユーザ名とパスワードはそれぞれUSERNAMEとPASSWORDで指定しますので、ここを適宜編集してください。
XSERVERNUMはXvfbのディスプレイ番号を指定します。
DBPATHはSkypeが設定情報などを書き込む先のディレクトリです。これもDAEMON_USERで読み書きできるようにしておいてください。
このスクリプトで起動されたskypeの環境にx11vncでアクセスするには、次のようにします。
$ sudo -u skype x11vnc -xauth /var/run/skype/Xauthority
初期起動時の設定
初期起動時、VNCクライアントで接続すると次のような画面となっているはずです。
ここで[I agree]を選択すると、自動的にログインが開始されます。
次に、Skype4Pyをインタラクティブモードで起動して、Skype Public APIへのアクセス許可を設定します。
x11vncの起動で指定したように、envコマンドなどでDISPLAY環境変数とXAUTHORITY環境変数をあらかじめ設定した状態でpythonを起動する必要があります。
$ env DISPLAY=:20 XAUTHORITY=/var/run/skype/Xauthority python Python 2.6.5 (r265:79063, Apr 16 2010, 13:57:41) [GCC 4.4.3] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import Skype4Py >>> skype = Skype4Py.Skype() >>> skype.Attach()
前節で説明したように一旦Attach()でブロックした状態になりますので、ここでVNCを使ってプロンプトを操作します。
必ず[Remember this selection]にチェックを入れてから[Yes]を選択してください。でないと、botを起動するたび、毎度この操作をVNCから行わなくてはならなくなってしまいます。
以上で初期起動時の設定は完了です。
おわりに
Skype public APIやその利用方法について解説している日本語の情報があまりないため、敷居が高くなっていた印象を受けますが、この記事が少しでも足しになれば幸いです。
注意点として、Skypeは基本的にP2Pアプリケーションなので、一般的なチャットクライアントとは違い、勝手にさまざまなノードと接続を開始してしまいます。サーバで運用するときは、そのサーバの属するネットワークの管理者に管理ポリシーなどを確認したほうがよいでしょう。
ちなみに、今のところ、さくらのVPS、EC2でSkypeを動かすことができています。
*1:http://twitter.com/shin_no_suke/status/25658845058
*2:http://www.slideshare.net/bpstudy/bpstudy36-beproudbot-5319457
php_threading
なんか完全に冗談のつもりで作った threading 拡張モジュールを、Alec さんという方がつい本気にして Windows にポートしたそうです (しかもネイティブスレッド使って)。
http://news.php.net/php.internals/49486
参照カウントをアトミックに増減させるとかそういう点を考慮してないから、アレだと思うんだけどなあ。
sotarokを勝手に祝う会
sotarokを勝手に祝う会に行ってまいりました!
僭越ながらこのたびはLTをさせていただいたので、その内容の紹介をさせていただきたく。(日立風)
皆様ご存知のことと思いますが、OSSコミュニティーにおいて、開発者が結婚する際には、新郎と新婦に
オリジナル○○○○○○○○○○○
を贈呈するという慣習があります。
はい、もちろん
オリジナルApacheモジュール
ですね。
というわけで、今回は
- mod_sotarok
- mod_mikko
を作らせていただきました。
mod_sotarok-mod_mikko at github.com
mod_sotarok.c:
/* * Copyright (c) 2010 Moriyoshi Koizumi * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "httpd.h" #include "http_config.h" #include "http_core.h" #include "http_log.h" #include "http_main.h" #include "http_protocol.h" #include "http_request.h" #include "util_script.h" #include "http_connection.h" #include "apr_strings.h" #include <stdio.h> module AP_MODULE_DECLARE_DATA sotarok_module; static const char *data[] = { "VAAAAAgAAAABAAEAAAADAAEAAQAAAAIAAQADAAAAAgABAAQAAAACAAEABAAAAAIAAQABAAAAAwABAAEAAAAH", "AAEAAQAAAAMAAQABAAAAAQABAAUAAAABAAEABAAAAAIAAQAEAAAAAwABAAMAAAACAAEAAQAAAAMAAQABAAAA", "AgABAAQAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAABAAEAAQAAAAMAAQABAAAAAQABAAEAAAADAAEAAQAA", "AAEAAQABAAAAAwABAAEAAAABAAEAAQAAAAMAAQABAAAABwABAAEAAAADAAEAAQAAAAEAAQABAAAABQABAAEA", "AAADAAEAAQAAAAEAAQABAAAAAwABAAEAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAABAAEAAQAAAAcAAQAB", "AAAAAwABAAEAAAADAAEAAQAAAAEAAQABAAAAAwABAAEAAAABAAEAAQAAAAMAAQABAAAAAQABAAEAAAADAAEA", "AQAAAAIAAQABAAAAAQABAAEAAAAIAAEAAQAAAAEAAQABAAAAAQABAAEAAAABAAEAAQAAAAUAAQABAAAAAwAB", "AAEAAAABAAEAAQAAAAMAAQABAAAAAwABAAEAAAADAAEAAgAAAAIAAQABAAAAAQABAAEAAAAHAAEAAQAAAAMA", "AQAFAAAAAQABAAUAAAABAAEABAAAAAIAAQAEAAAAAwABAAEAAAABAAEAAQAAAAgAAQABAAAAAQABAAEAAAAB", "AAEAAQAAAAEAAQAEAAAAAgABAAEAAAADAAEAAQAAAAEAAQABAAAAAwABAAEAAAADAAEAAQAAAAMAAQABAAAA", "AQABAAEAAAABAAEAAQAAAAEAAQABAAAAAQABAAMAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAABAAEAAQAA", "AAMAAQABAAAAAQABAAEAAAAFAAEAAQAAAAcAAQABAAAACQABAAEAAAABAAEAAQAAAAEAAQABAAAAAQABAAEA", "AAAFAAEAAQAAAAMAAQABAAAAAQABAAEAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAACAAEAAgAAAAEAAQAB", "AAAAAwABAAEAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAABAAEAAQAAAAMAAQABAAAAAQABAAEAAAAFAAEA", "AQAAAAcAAQABAAAACQABAAEAAAABAAEAAQAAAAEAAQABAAAAAQABAAEAAAAFAAEAAQAAAAMAAQABAAAAAQAB", "AAEAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAADAAEAAQAAAAEAAQABAAAAAwABAAEAAAAHAAEAAQAAAAMA", "AQABAAAAAQABAAEAAAADAAEAAQAAAAEAAQABAAAABQABAAEAAAAHAAEAAQAAAAoAAQABAAAAAQABAAEAAAAC", "AAEABQAAAAEAAQAEAAAAAgABAAQAAAADAAEAAwAAAAIAAQABAAAAAwABAAEAAAACAAEABAAAAAMAAQABAAAA", "AwAAAFQAAAAAAA==" }; static int sotarok_handler(request_rec *r) { int i; if (strcmp(r->handler, "sotarok-handler")) { return DECLINED; } ap_set_content_type(r, "text/plain; charset=US-ASCII"); if (r->header_only) return OK; for (i = 0; i < sizeof(data) / sizeof(data[0]); i++) { ap_rputs(data[i], r); ap_rputs("\n", r); } return OK; } static void sotarok_register_hooks(apr_pool_t *p) { ap_hook_handler(sotarok_handler, NULL, NULL, APR_HOOK_MIDDLE); } module AP_MODULE_DECLARE_DATA sotarok_module = { STANDARD20_MODULE_STUFF, NULL, /* per-directory config creator */ NULL, /* dir config merger */ NULL, /* server config creator */ NULL, /* server config merger */ NULL, /* command table */ sotarok_register_hooks /* set up other request processing hooks */ };
mod_mikko.c
/* * Copyright (c) 2010 Moriyoshi Koizumi * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include <assert.h> #include "httpd.h" #include "http_config.h" #include "http_core.h" #include "http_log.h" #include "http_main.h" #include "http_protocol.h" #include "http_request.h" #include "util_script.h" #include "http_connection.h" #include "apr_strings.h" #include <stdio.h> module AP_MODULE_DECLARE_DATA mikko_module; static unsigned int read_u32le(const char *p) { return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); } static unsigned int read_u16le(const char *p) { return p[0] | (p[1] << 8); } static char *base64_decode_all(apr_pool_t *pool, const char *buf, apr_size_t buf_len, apr_size_t *result_len) { const char *p, *e = buf + buf_len; char *result, *q; q = result = apr_palloc(pool, (buf_len / 3) * 4); p = buf; while (p < e) { int l = apr_base64_decode(q, p); if (l == 0) p++; else { assert(l > 0); q += l; p += ((l + 2) / 3) * 4; } } *result_len = q - result; return result; } static apr_status_t mikko_do_out_filter(ap_filter_t *f, apr_bucket_brigade *bb) { static const char prologue[] = "<html>\n\ <head>\n\ <title>Happy Wedding!</title>\n\ <style type=\"text/css\">\n\ .colored { color: red; }\n\ .uncolored { color: lightgray; }\n\ </style>\n\ </head>\n\ <body>\n\ <pre>"; static const char epilogue[] = "</pre>\n\ </body>\n\ </html>\n"; static const char span_colored_open[] = "<span class=\"colored\">"; static const char span_uncolored_open[] = "<span class=\"uncolored\">"; static const char span_close[] = "</span>"; apr_status_t err = APR_SUCCESS; char *buf = 0; apr_size_t buf_len = 0; char *decoded_data = 0; unsigned int width, height; const char *p, *pe; const char *q; err = apr_brigade_pflatten(bb, &buf, &buf_len, f->r->pool); if (err) return err; apr_brigade_cleanup(bb); APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_immortal_create( prologue, sizeof(prologue) - 1, f->c->bucket_alloc)); { apr_size_t decoded_len; p = decoded_data = base64_decode_all(f->r->pool, buf, buf_len, &decoded_len); pe = decoded_data + decoded_len; } if (p >= pe) goto error; width = read_u32le(p), p += 4; if (p >= pe) goto error; height = read_u32le(p), p += 4; q = buf; for (;;) { int v, c; if (p >= pe) goto error; v = read_u16le(p), p += 2; if (p >= pe) goto error; c = read_u16le(p), p += 2; if (!c) break; if (v) { APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_immortal_create( span_colored_open, sizeof(span_colored_open) - 1, f->c->bucket_alloc)); APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_transient_create(q, c, f->c->bucket_alloc)); APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_immortal_create( span_close, sizeof(span_close) - 1, f->c->bucket_alloc)); } else { APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_immortal_create( span_uncolored_open, sizeof(span_uncolored_open) - 1, f->c->bucket_alloc)); APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_transient_create(q, c, f->c->bucket_alloc)); APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_immortal_create( span_close, sizeof(span_close) - 1, f->c->bucket_alloc)); } q += c; while (*q == '\n') { APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_transient_create(q, 1, f->c->bucket_alloc)); q++; } } APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_immortal_create( span_uncolored_open, sizeof(span_uncolored_open) - 1, f->c->bucket_alloc)); APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_transient_create( q, buf_len - (q - buf), f->c->bucket_alloc)); APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_immortal_create( span_close, sizeof(span_close) - 1, f->c->bucket_alloc)); APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_immortal_create( epilogue, sizeof(epilogue) - 1, f->c->bucket_alloc)); APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_eos_create(f->c->bucket_alloc)); f->r->content_type = "text/html; charset=US-ASCII"; return ap_pass_brigade(f->next, bb); error: APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_immortal_create( "error", sizeof("error") - 1, f->c->bucket_alloc)); APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_eos_create(f->c->bucket_alloc)); return ap_pass_brigade(f->next, bb); } static void mikko_register_hooks(apr_pool_t *p) { ap_register_output_filter("MIKKO_OUT", mikko_do_out_filter, NULL, AP_FTYPE_RESOURCE); } module AP_MODULE_DECLARE_DATA mikko_module = { STANDARD20_MODULE_STUFF, NULL, /* per-directory config creator */ NULL, /* dir config merger */ NULL, /* server config creator */ NULL, /* server config merger */ NULL, /* command table */ mikko_register_hooks /* set up other request processing hooks */ };
mod_sotarokを次のように組み込んで
LoadModule sotarok_module .libs/mod_sotarok.so Listen 8080 <Location /> SetHandler sotarok-handler </Location> PidFile /tmp/httpd.pid LockFile /tmp/accept.lock ErrorLog /tmp/error_log
Apacheにリクエストを投げると、次のような文字列が帰ってきます。
VAAAAAgAAAABAAEAAAADAAEAAQAAAAIAAQADAAAAAgABAAQAAAACAAEABAAAAAIAAQABAAAAAwABAAEAAAAH AAEAAQAAAAMAAQABAAAAAQABAAUAAAABAAEABAAAAAIAAQAEAAAAAwABAAMAAAACAAEAAQAAAAMAAQABAAAA AgABAAQAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAABAAEAAQAAAAMAAQABAAAAAQABAAEAAAADAAEAAQAA AAEAAQABAAAAAwABAAEAAAABAAEAAQAAAAMAAQABAAAABwABAAEAAAADAAEAAQAAAAEAAQABAAAABQABAAEA AAADAAEAAQAAAAEAAQABAAAAAwABAAEAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAABAAEAAQAAAAcAAQAB AAAAAwABAAEAAAADAAEAAQAAAAEAAQABAAAAAwABAAEAAAABAAEAAQAAAAMAAQABAAAAAQABAAEAAAADAAEA AQAAAAIAAQABAAAAAQABAAEAAAAIAAEAAQAAAAEAAQABAAAAAQABAAEAAAABAAEAAQAAAAUAAQABAAAAAwAB AAEAAAABAAEAAQAAAAMAAQABAAAAAwABAAEAAAADAAEAAgAAAAIAAQABAAAAAQABAAEAAAAHAAEAAQAAAAMA AQAFAAAAAQABAAUAAAABAAEABAAAAAIAAQAEAAAAAwABAAEAAAABAAEAAQAAAAgAAQABAAAAAQABAAEAAAAB AAEAAQAAAAEAAQAEAAAAAgABAAEAAAADAAEAAQAAAAEAAQABAAAAAwABAAEAAAADAAEAAQAAAAMAAQABAAAA AQABAAEAAAABAAEAAQAAAAEAAQABAAAAAQABAAMAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAABAAEAAQAA AAMAAQABAAAAAQABAAEAAAAFAAEAAQAAAAcAAQABAAAACQABAAEAAAABAAEAAQAAAAEAAQABAAAAAQABAAEA AAAFAAEAAQAAAAMAAQABAAAAAQABAAEAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAACAAEAAgAAAAEAAQAB AAAAAwABAAEAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAABAAEAAQAAAAMAAQABAAAAAQABAAEAAAAFAAEA AQAAAAcAAQABAAAACQABAAEAAAABAAEAAQAAAAEAAQABAAAAAQABAAEAAAAFAAEAAQAAAAMAAQABAAAAAQAB AAEAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAADAAEAAQAAAAEAAQABAAAAAwABAAEAAAAHAAEAAQAAAAMA AQABAAAAAQABAAEAAAADAAEAAQAAAAEAAQABAAAABQABAAEAAAAHAAEAAQAAAAoAAQABAAAAAQABAAEAAAAC AAEABQAAAAEAAQAEAAAAAgABAAQAAAADAAEAAwAAAAIAAQABAAAAAwABAAEAAAACAAEABAAAAAMAAQABAAAA AwAAAFQAAAAAAA==
見たところ base64 なわけですが、これをデコードしてみると、
$ php -r 'echo bin2hex(base64_decode(file_get_contents("/tmp/out.txt")));' 540000000800000001000100000003000100010000000200010003000000020001000400000002000100040000000200010001000000030001000100000007000100010000000300010001000000010001000500000001000100040000000200010004000000030001000300000002000100010000000300010001000000020001000400000003000100010000000300010001000000030001000100000001000100010000000300010001000000010001000100000003000100010000000100010001000000030001000100000001000100010000000300010001000000070001000100000003000100010000000100010001000000050001000100000003000100010000000100010001000000030001000100000003000100010000000300010001000000030001000100000001000100010000000700010001000000030001000100000003000100010000000100010001000000030001000100000001000100010000000300010001000000010001000100000003000100010000000200010001000000010001000100000008000100010000000100010001000000010001000100000001000100010000000500010001000000030001000100000001000100010000000300010001000000030001000100000003000100020000000200010001000000010001000100000007000100010000000300010005000000010001000500000001000100040000000200010004000000030001000100000001000100010000000800010001000000010001000100000001000100010000000100010004000000020001000100000003000100010000000100010001000000030001000100000003000100010000000300010001000000010001000100000001000100010000000100010001000000010001000300000003000100010000000300010001000000030001000100000001000100010000000300010001000000010001000100000005000100010000000700010001000000090001000100000001000100010000000100010001000000010001000100000005000100010000000300010001000000010001000100000003000100010000000300010001000000030001000100000002000100020000000100010001000000030001000100000003000100010000000300010001000000030001000100000001000100010000000300010001000000010001000100000005000100010000000700010001000000090001000100000001000100010000000100010001000000010001000100000005000100010000000300010001000000010001000100000003000100010000000300010001000000030001000100000003000100010000000100010001000000030001000100000007000100010000000300010001000000010001000100000003000100010000000100010001000000050001000100000007000100010000000a0001000100000001000100010000000200010005000000010001000400000002000100040000000300010003000000020001000100000003000100010000000200010004000000030001000100000003000000540000000000
のように、何か意味ありげなオクテット列が続いていることが分かります。
では、mod_mikko.so を組み込んでみましょう。
LoadModule sotarok_module .libs/mod_sotarok.so LoadModule mikko_module .libs/mod_mikko.so Listen 8080 <Location /> SetHandler sotarok-handler SetOutputFilter MIKKO_OUT </Location> PidFile /tmp/httpd.pid LockFile /tmp/accept.lock ErrorLog /tmp/error_log
出力:
VAAAAAgAAAABAAEAAAADAAEAAQAAAAIAAQADAAAAAgABAAQAAAACAAEABAAAAAIAAQABAAAAAwABAAEAAAAH
AAEAAQAAAAMAAQABAAAAAQABAAUAAAABAAEABAAAAAIAAQAEAAAAAwABAAMAAAACAAEAAQAAAAMAAQABAAAA
AgABAAQAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAABAAEAAQAAAAMAAQABAAAAAQABAAEAAAADAAEAAQAA
AAEAAQABAAAAAwABAAEAAAABAAEAAQAAAAMAAQABAAAABwABAAEAAAADAAEAAQAAAAEAAQABAAAABQABAAEA
AAADAAEAAQAAAAEAAQABAAAAAwABAAEAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAABAAEAAQAAAAcAAQAB
AAAAAwABAAEAAAADAAEAAQAAAAEAAQABAAAAAwABAAEAAAABAAEAAQAAAAMAAQABAAAAAQABAAEAAAADAAEA
AQAAAAIAAQABAAAAAQABAAEAAAAIAAEAAQAAAAEAAQABAAAAAQABAAEAAAABAAEAAQAAAAUAAQABAAAAAwAB
AAEAAAABAAEAAQAAAAMAAQABAAAAAwABAAEAAAADAAEAAgAAAAIAAQABAAAAAQABAAEAAAAHAAEAAQAAAAMA
AQAFAAAAAQABAAUAAAABAAEABAAAAAIAAQAEAAAAAwABAAEAAAABAAEAAQAAAAgAAQABAAAAAQABAAEAAAAB
AAEAAQAAAAEAAQAEAAAAAgABAAEAAAADAAEAAQAAAAEAAQABAAAAAwABAAEAAAADAAEAAQAAAAMAAQABAAAA
AQABAAEAAAABAAEAAQAAAAEAAQABAAAAAQABAAMAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAABAAEAAQAA
AAMAAQABAAAAAQABAAEAAAAFAAEAAQAAAAcAAQABAAAACQABAAEAAAABAAEAAQAAAAEAAQABAAAAAQABAAEA
AAAFAAEAAQAAAAMAAQABAAAAAQABAAEAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAACAAEAAgAAAAEAAQAB
AAAAAwABAAEAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAABAAEAAQAAAAMAAQABAAAAAQABAAEAAAAFAAEA
AQAAAAcAAQABAAAACQABAAEAAAABAAEAAQAAAAEAAQABAAAAAQABAAEAAAAFAAEAAQAAAAMAAQABAAAAAQAB
AAEAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAADAAEAAQAAAAEAAQABAAAAAwABAAEAAAAHAAEAAQAAAAMA
AQABAAAAAQABAAEAAAADAAEAAQAAAAEAAQABAAAABQABAAEAAAAHAAEAAQAAAAoAAQABAAAAAQABAAEAAAAC
AAEABQAAAAEAAQAEAAAAAgABAAQAAAADAAEAAwAAAAIAAQABAAAAAwABAAEAAAACAAEABAAAAAMAAQABAAAA
AwAAAFQAAAAAAA==
というわけで、
sotarok & komikko、おめでとうございます!末永くお幸せに!
『プログラミングの魔導書 〜Programmers' Grimoire〜』がすごいのです
すでに多くの方がご存知のことと思いますが、株式会社ロングゲートからまもなく『プログラミングの魔導書 〜Programmers' Grimoire〜』が発刊されます。
発売前レビューをするということで、id:faith_and_brave さんより PDF を頂戴していたのですが、まったく自分の役割を自覚していなかった (内容のチェック的なものかと思ってました) ので、予約締切間近 (8/6、つまり明日までです!) ですが、ちょっと紹介させてもらいますね。
Bjarne Stroustrupへのインタビュー
デンマーク生まれの長男で髪の毛が年々少なくなっているというビャーネ・ストロストォプさんへのインタビューです。インタビュワーは、丸刈りで丸刈りでC++が好きで「本の虫」というブログを執筆されている、江添さんです。
C++0xの現況をざっくり把握するのに、どこを見るのが一番なんだろうか、と悩んでしまいますが、インタビューの書き起こしというのはとてもいいですね。これを読むだけでも価値があると思いました。
「C++0x以降の進化について」というトピックで、ストロストォプさんは「multimethods」*1 を推しているのですが、これが個人的には興味深いところでした。
ダイナミックディスパッチをRTTIに基づいて行うのに、仮想関数を用いるのは、クラスの設計の柔軟性を妨げます。特に、値だけを保持するようなポリモーフィックなクラスに、ディスパッチのためだけに仮想関数を追加していくやり方は、ドメインロジックの記述を分散化させ、設計を困難にします。 (と、仕事をしていて思った。)
さらにストロストォプさんが指摘するように、確かにこの問題はVisitorパタンを使えば部分的に解決できますが、それでも、Visitorをacceptするためのメソッドを操作対象となるクラスにいちいち付加しないといけない、という問題がありますし、この状況で出現するVisitorというのは、ドメインロジックの構成要素ではないので、設計上の邪魔になります。
multimethodsを使うことで、ますますC++のコードが素人目にメンテ不可能になりそうな気もしないではありませんが、個人的には今すぐにでも欲しい機能です。
ところで、『ムーブ』についても触れていますね。「C++で書くと遅い!」というCプログラマの詭弁に最後まで抗えなかったのが、Cでは当たり前のムーブセマンティクスなのですが、これもムーブ演算子、ムーブコンストラクタの導入により、素直に、less error-proneな形で実現できるというのが気に入っています。
Boost.Serialization の紹介
オブジェクトを手軽に永続化するのに便利な Boost.Serialization の紹介です。残念ながら、私はちょっとしか使ったことがないのですが、記事もコンパクトにまとまっているように、予想以上に手軽に使えることは間違いないです。
Variadic Templates - お前を待っていた
メタプログラミングについて理解がないと、可変長テンプレートのありがたみというのは湧いてこない気もしていたのですが、この記事では、Win32 APIに基づいた実践的な例を出して解説しています。
Chronoライブラリで考える型システム
C++ では、型システムと、それを取り巻く操作を扱うのに、traits という概念が出てきますが、これって「圏」のイメージにつながるなあ、と思いました。
オーブンレンジクッキング
id:mb2sync さんの C++ ライブラリ集である P-Stade の一様を担う Oven についての詳細な解説記事。Range だから Oven。Oven とても仕事で使いたんですけど、Boost 標準的でないという理由でだめなんですよね…。RangeEx には期待したんですが、Oven のこなれた感には全く及んでおらず落胆しました。俺プロジェクトでは、バンバン使っていきたいです。
C++ を実践的に扱えるようになるのに、とても大事な概念だと思います。Range。Java でも Collections Framework が扱えて一人前みたいのがあったと思いますが、それに匹敵しますね。なので、これを機に Range に触れてみるというのはいい話だと思いますよ。
Hello, C++ World!
C++で『Hello, World』が実現されるまでの、男たちのドラマについて、稲葉さんが淡々と語ります。
C++ほどライブラリの作り手の心をくすぐる言語って他にないと思うんですよね。使い手には楽をさせてやろう、驚かせてやろう、という一心が、様々な技工と、数多くの非常に完成度の高いライブラリを生んできたのだと勝手に思っているわけですが、そんな一端が、ただの『Hello, World』に垣間見れた、という気がいたしました。
Crawling in the Stream
上のような感想を抱く理由は、まあ、iostream の設計を見るとよく分かるワケで、そうですね、やりすぎないようにするのが大事なのかな、と思います。
メタプログラミングノキワミ アッー!
sizeof() の使いどころ、Boost.Preprocessor の使いどころ、という感じでしょうか。メタプログラミングに馴染みのない方にとっては、ずっと頭の体操になってしまうかもしれないので、もうちょっと入門的な記事があってもいいのかな、と思ってしまいました。
Boost.AsioによるHTTP通信入門
ネットワークライブラリ、C界隈だと、libevent とか libev とか picoev とかいろいろあるわけですが、C++ ではどうなの、というわけで、Boost.Asio をオススメしたいと思っている今日この頃ですが、いかにせんドキュメントがない。そういう中で、この記事はかなりいいと思います。長すぎない、ちょうどいい分量のコードを使って解説がされている、という印象を受けました。
C++の歴史、BoostCon 2010 体験記
読み物ということでレビューは割愛しますw
まとめ
Effective C++ 原著第3版 (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
- 作者: スコット・メイヤーズ,小林健一郎
- 出版社/メーカー: ピアソン・エデュケーション
- 発売日: 2006/04/29
- メディア: 大型本
- 購入: 26人 クリック: 363回
- この商品を含むブログ (178件) を見る
Modern C++ Design―ジェネリック・プログラミングおよびデザイン・パターンを利用するための究極のテンプレート活用術 (C++ In‐Depth Series)
- 作者: アンドレイアレキサンドレスク,Andrei Alexandrescu,村上雅章
- 出版社/メーカー: ピアソンエデュケーション
- 発売日: 2001/12
- メディア: 単行本
- 購入: 10人 クリック: 204回
- この商品を含むブログ (99件) を見る
Exceptional C++―47のクイズ形式によるプログラム問題と解法 (C++ in‐Depth Series)
- 作者: ハーブサッター,浜田光之,Harb Sutter,浜田真理
- 出版社/メーカー: ピアソンエデュケーション
- 発売日: 2000/11
- メディア: 単行本
- 購入: 7人 クリック: 115回
- この商品を含むブログ (65件) を見る
- 作者: Scott Meyers
- 出版社/メーカー: Addison-Wesley Professional
- 発売日: 1995/12/29
- メディア: ペーパーバック
- クリック: 21回
- この商品を含むブログ (10件) を見る
『Effective C++』『Modern C++ Design』『Exceptional C++』なんかを読んで、「はぁ、C++ってこういう言語だったんだ」と、C++を再発見したけれど、周りにそういう書き方実践している人いないし…と寂しい思いをしている人に。もちろん、これらの書籍を一通り読んだ後のために積ん読しておくのにも、おすすめです。
LLの虎 準決勝: 殺伐Python - 「タイガー&ドラゴン」
資料とパッチを公開します。
Puby の方の解説とスクリーンキャストは
「Python の文法だけを Ruby っぽくしてみたらどうなるか実験した」にあります。
Rhython の方のパッチは
Gist:502964
です。
LLTigerで発表しました -Language Update編-
資料あげときます。
Xlib.php は
http://github.com/moriyoshi/php-Xlib/
よりとってきてください。
<?php require 'Xlib.php'; $fonts = array( 'hiragino-mincho' => "/System/Library/Fonts/ヒラギノ明朝 ProN W6.otf", 'courier' => "/Library/Fonts/Courier New Bold.ttf", ); $texts = array( array("はい"), array("始まって"), array("しまいました"), array("Heavyweight"), array("Heavyweight\nTemplate"), array("Heavyweight\nTemplate\nLanguage"), array("PHPの"), array("Language\nUpdate"), array("今回は"), array("私"), array("moriyoshiが"), array("お送りさせて"), array("いただきます"), array("自己紹介"), array("<?php phpcredits(); ?>"), array("→"), array("さて"), array("PHPは実用本意"), array("進歩"), array("とくにありません"), array("バグフィックス"), array("もり"), array("もり・"), array("もり・もり"), array("もり・もり・"), array("もり・もり・もりだくさん♨"), array("…なわけですが"), array("昨年からの\n振り返り"), array("2009/6/30"), array("5.3.0リリース"), array("クロージャ導入\nネームスペース導入\netc."), array("\"), array("民主主義の悲劇"), array("…"), array("2009/7/16"), array("CVSから\nSubversionへの\n移行完了"), array("いまさらという感じ"), array("以降\nしばらく\n目立った動きなし"), array("…"), array("2010/3/12"), array("PHP6ブランチ放棄事件"), array("事の発端"), array("output-bufferingのバグ"), array("直すには大規模な\n改修が必要"), array("パッチはtrunkにある"), array("5.3にバックポートしたい"), array("5.3は安定ブランチ"), array("なので許されない"), array("trunkはUnicode対応作業中"), array("リリースは当分先"), array("他にも同様の事例あり"), array("しびれを切らしたJaniさん"), array("無断で"), array("trunkをリネームし\n5.3からtrunkを再ブランチ"), array("糾弾される"), array("そこにRasmusさん登場"), array("「まあまあ落ち着いて」"), array("一定の理解を示すよう提案"), array("結局"), array("Unicode化は見送られ"), array("新生trunk\n(5.4ブランチ)"), array("誕生"), array("というわけでした"), array("…"), array("2010/4/22"), array("Traits patchマージ"), array("Traitsとは?"), array("いわゆるMixin的なもの"), array("実装の再利用性を促進"), array("trait Hey {\n public function hey() {\n echo \"hey\\n\";\n }\n}", array('font' => 'courier')), array("trait Howdy {\n public function howdy() {\n echo \"howdy\\n\";\n }\n}", array('font' => 'courier')), array("class Hello {\n use Hey, Howdy;\n}", array('font' => 'courier')), array("class Hello {\n use Hey, Howdy;\n}\n\$hello = new Hello;\n\$hello->hey();\n\$hello->howdy();", array('font' => 'courier')), array("class TexasHello {\n use Hey;\n use Howdy { hello => howdy }\n}", array('font' => 'courier')), array("\$hello = new TexasHello;\n\$hello->howdy();\n\$hello->hello();", array('font' => 'courier')), array("…"), array("2010/7/22"), array("5.3.3リリース"), array("重要な変更"), array("メソッド名=クラス名\n☞コンストラクタ"), array(" ○\n(従来までの常識)"), array("but"), array("名前空間中のクラス"), array("×"), array("namespace Foo;\nclass Bar {\n public function Bar() {\n // 5.3.2まではコンストラクタ\n // 5.3.3からは普通のメソッド\n }\n}"), array("理由?"), array("嫌だから"), array("…"), array("以上で"), array("Language Update"), array("駆け足でしたが"), array("ご清聴ありがとうございました"), array("ところで"), array("この"), array("プレゼンツールは"), array("もちろん"), array("100% Pure PHPで"), array("実装しております"), array("- おわり -"), ); ini_set('memory_limit', -1); $x = XDisplay::create(); $bc = $x->allocColor($x->screens[0]->colormap, 0xffff, 0xffff, 0xffff); $wnd = $x->createWindow($x->screens[0]->rootWindow, 0, 0, $x->screens[0]->width, $x->screens[0]->height, 0, XClient::InputOutput, null, array('backgroundPixel' => $bc['pixel'], 'eventMask' => XClient::ExposureMask | XClient::ButtonPressMask | XClient::KeyPressMask | XClient::StructureNotifyMask, 'overrideRedirect' => true)); $_motif_wm_hints = $x->internAtom("_MOTIF_WM_HINTS"); $wnd->changeProperty(XClient::PropModeReplace, $_motif_wm_hints, $_motif_wm_hints, 32, "\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"); $gc = $wnd->createGC( array('function' => 3, 'lineWidth' => 8, 'lineStyle' => 0)); $x->mapWindow($wnd); class Canvas { protected $im; public $width; public $height; protected $backgroundColor; protected $foregroundColor; public function __construct($width, $height) { $this->im = imagecreatetruecolor($width, $height); $this->width = $width; $this->height = $height; $this->backgroundColor = imagecolorallocate($this->im, 255, 255, 255); $this->foregroundColor = imagecolorallocate($this->im, 0, 0, 0); } public function toPixels() { ob_start(); imagegd2($this->im, NULL, 64); $data = substr(ob_get_clean(), 23); $data |= substr($data & str_repeat("\x00\x00\x00\xff", strlen($data) / 4), 3); return $data; } public function clear() { imagefilledrectangle($this->im, 0, 0, $this->width, $this->height, $this->backgroundColor); } public function setFont($font) { $this->font = $font; } public function drawText($text, $sz) { for (;;) { $bbox = imagettfbbox($sz, 0, $this->font, $text); if ($bbox[2] < $this->width * 0.8) break; $sz -= 8; } imagettftext($this->im, $sz, 0, ($this->width - $bbox[2]) / 2, ($this->height - $bbox[3]) / 2 + $sz * 0.6, $this->foregroundColor, $this->font, $text); } } $cv = new Canvas($x->screens[0]->width, $x->screens[0]->height); function drawImage($wnd, $gc, $x, $y, $width, $height, $data) { $ncx = (int)($width / 64); $ncy = (int)($height / 64); for ($j = 0; $j < $ncy; $j++) { $ch = min(64, $height - $j * 64); for ($i = 0; $i < $ncx; $i++) { $cw = min(64, $width - $i * 64); $wnd->putImage(2, $gc, $cw, $ch, $x + $i * 64, $y + $j * 64, 0, 24, substr($data, $i * 16384 + $j * $width * 256, 16384)); } } } $data = null; $counter = 0; for (;;) { switch ($x->nextEvent($ev)) { case XClient::Expose: if ($data !== null) drawImage($wnd, $gc, 0, 0, $cv->width, $cv->height, $data); break; case XClient::ButtonPress: case XClient::KeyPress: case XClient::KeyRelease: $cv->clear(); $line = array_shift($texts); if ($line) { $text = $line[0]; $fontname = 'hiragino-mincho'; if (isset($line[1])) { if (isset($line[1]['font'])) $fontname = $line[1]['font']; } $cv->setFont($fonts[$fontname]); $cv->drawText($text, 84); } $data = $cv->toPixels(); drawImage($wnd, $gc, 0, 0, $cv->width, $cv->height, $data); break; case XClient::MotionNotify: break; } }