JJUG CCC 2009 Fall でライトニングトークして来たよ!

JJUG

台風18号が通過する中、JJUG Cross Community Conference 2009 Fall が開催されました。

雨はすぐ止んで青空でしたが、風が超強かった!

JJUGの概要は、JJUG Cross Community Conference 2009 Fall - kagamihogeのblogとか見てください><

LT

今回は、天下一15分ですべてを見せてやる&ライトニングトーク大会で発表する側で参加しました。発表したスライドを公開しておきますね。Google++

会場に着いたら、@yamashiro が念入りに練習しているのが印象に残りました!
自分はろくに練習もしないでぶっつけ本番でしたが、5分ピッタリで終われました!

懇親会

今日の飲み会はJJUG枠で、昼間流れた@yoshioriつぶやきに反応したら参加可能という敷居の高さでしたが、こっそり参加してきました。岡崎さんごめんなさい!

Twitter4Jで日本中につぶやく

TwitterへのつぶやきにTwitter4Jを使わせてもらってるんだけど、Twitter API 互換のはずの他のサービスでは使えないのかな?
Wassrや、はてなハイクは、Twitter API 互換のはずなんだけど、タイムラインの表示もつぶやきの更新もできやしないよ!

はてなブックマーク Web Hookで、はてなブックマークをパトロールしよう!

自作IRCライブラリを使ってみよう!ってことで、はてなブックマーク Web Hookを使って、ブックマークをIRCでヲチするアプリケーション「はてなパトロール」を作りました!

はてなWebHookの設定方法は、公式ページを参考にしてください!

このアプリケーションを実行するのに必要なライブラリは、IRCKitとJettyだけです。正常に起動できると、指定されたチャンネルに「Welcome to WebHook.」とメッセージを表示します。あとは、いつも通りはてなブックマークをすれば、ブックマークをIRCに表示します。簡単ですね。

/*
 *  Copyright (c) 2009 tarchan. All rights reserved.
 */
package com.mac.tarchan.webhook;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.mortbay.jetty.Connector;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.nio.SelectChannelConnector;
import org.mortbay.jetty.servlet.ServletHandler;
import org.mortbay.jetty.servlet.ServletHolder;

import com.mac.tarchan.net.irc.client.IRCClient;
import com.mac.tarchan.net.irc.client.IRCMessage;
import com.mac.tarchan.net.irc.client.Reply;

/**
 * はてなパトロールは、はてなWebHookを使用したサービスです。
 * 
 * @author tarchan
 */
public class HatetaPatrol extends HttpServlet
{
	/** WebHookのキー */
	String webhookKey = "WebHookのキー";

	/** WebHookのパス */
	String webhookPath = "WebHookのパス";

	/** WebHookのポート */
	int webhookPort = <WebHookのポート>;

	/** IRCクライアント */
	transient IRCClient irc;

	/** IRCサーバー */
	String ircHost = "IRCサーバー";

	/** IRCサーバーのポート */
	String ircPort = "IRCサーバーのポート";

	/** IRCチャンネル */
	String ircChannel = "IRCチャンネル";

	/** 文字エンコーディング */
	String ircEncoding = "文字エンコーディング";

	/** IRCニックネーム */
	String ircNick = "IRCニックネーム";

	/**
	 * はてなパトロールを開始します。
	 * 
	 * @param args なし
	 */
	public static void main(String[] args)
	{
		try
		{
			HatetaPatrol webhook = new HatetaPatrol();
			webhook.startIRC();
			webhook.startServlet();
		}
		catch (Exception x)
		{
			x.printStackTrace();
		}
	}

	/**
	 * IRCを開始します。
	 * 
	 * @throws IOException IRCサーバーに接続できない場合
	 */
	synchronized void startIRC() throws IOException
	{
		irc = new IRCClient()
			.setProperty("irc.host", ircHost)
			.setProperty("irc.port", ircPort)
			.setProperty("irc.channel", ircChannel)
			.setProperty("irc.encoding", ircEncoding)
			.setProperty("irc.nick.name", ircNick)
			.addAllHandlers(this);
		irc.open();
	}

	/**
	 * WebHookのリクエストを受けるサービスを開始します。
	 * 
	 * @throws Exception Webサーバーが開始できない場合
	 */
	void startServlet() throws Exception
	{
		ServletHolder servletHolder = new ServletHolder(this);
		ServletHandler servletHandler = new ServletHandler();
		servletHandler.addServletWithMapping(servletHolder, webhookPath);

		SelectChannelConnector connector = new SelectChannelConnector();
		connector.setPort(webhookPort);
		Server server = new Server();
		server.setConnectors(new Connector[]{connector});
		server.addHandler(servletHandler);
		server.start();
	}

	/**
	 * IRC接続のウェルカムメッセージを表示します。
	 * 
	 * @param msg IRCメッセージ
	 */
	@Reply("001")
	public void onWelcome(IRCMessage msg)
	{
		String welcome = "Welcome to WebHook.";
		log(welcome);
		doNotice(ircChannel, welcome);
	}

	/**
	 * IRCメッセージをロギングします。
	 * 
	 * @param msg IRCメッセージ
	 */
	@Reply("ALL")
	public void onMessage(IRCMessage msg)
	{
		// エラーでない場合は何もしない
		if (msg.isNumelicReply() && msg.getNumber() < 400) return;
		if (!msg.getCommand().equals("ERROR")) return;

		log(msg.toString());
	}

	/**
	 * IRCにメッセージを送信します。
	 * 
	 * @param channel チャンネル
	 * @param str メッセージ
	 */
	synchronized void doNotice(String channel, String str)
	{
		String msg = String.format("PRIVMSG %s :%s", channel, str);
		irc.postMessage(msg);
	}

	/**
	 * はてなWebHookを処理します。
	 */
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
	{
		req.setCharacterEncoding("UTF-8");
		String username = req.getParameter("username");
		String title = req.getParameter("title");
		String url = req.getParameter("url");
		String status = req.getParameter("status");
		String comment = req.getParameter("comment");
		String key = req.getParameter("key");

		// APIキーを確認
		if (!key.equals(webhookKey))
		{
			res.sendError(HttpServletResponse.SC_BAD_REQUEST);
			return;
		}

		// はてなブックマークを送信
		String bookmark = String.format("[%s] %s %s %s", username, title, url, comment);
		log(String.format("[%S] %s", status, bookmark));
		doNotice(ircChannel , bookmark);
	}

	/**
	 * ロギングします。
	 */
	@Override
	public void log(String msg)
	{
		System.out.println(msg);
	}
}

IRCボットの実装で見るJava言語

ネットワーク対戦ゲームの募集文をNOTICE発言するボットをJavaで実装してみた。IRCライブラリは自作のIRCKitを使用しました。

ボットのソースコードは次のとおりです。

/*
 * Copyright (c) 2009 tarchan. All rights reserved.
 */
package com.mac.tarchan.ircbox;

import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;

import com.mac.tarchan.net.irc.client.IRCClient;
import com.mac.tarchan.net.irc.client.IRCMessage;
import com.mac.tarchan.net.irc.client.Reply;

/**
 * GameOffer
 * 
 * @author tarchan
 */
public class GameOffer
{
	/**
	 * @param args 設定ファイル名
	 */
	public static void main(String[] args)
	{
		try
		{
			new GameOffer(args[0]);
		}
		catch (IOException x)
		{
			x.printStackTrace();
		}
	}

	/** IRCクライアント */
	private IRCClient irc;

	/**
	 * GameOffer を構築します。
	 * 
	 * @param name 設定ファイル名
	 * @throws IOException 入出力エラーが発生した場合
	 */
	public GameOffer(String name) throws IOException
	{
		irc = new IRCClient().load(name).addAllHandlers(this);
		irc.open();
	}

	/**
	 * メッセージを解析します。
	 * 
	 * @param msg IRCメッセージ
	 */
	@Reply("PRIVMSG")
	public void onMessage(IRCMessage msg)
	{
		String input = msg.getTrail();
		if (input.matches("^[1-9]\\d{3,4}$"))
		{
			postGameOffer(msg);
		}
		else if (input.matches("^good night, jewel.$"))
		{
			postQuit(msg);
		}
	}

	/** 募集中メッセージを送信します。 */
	private void postGameOffer(IRCMessage msg)
	{
		String channel = msg.getParam(0);		// msg.channel
		String nick = msg.getPrefix().getNick();	// msg.prefix.nick
		String host = msg.getPrefix().getHost();	// msg.prefix.host
		String port = msg.getTrail();			// msg.trail
		host = dnsLookup(host);
		irc.postMessage(String.format("NOTICE %s :%s さんが %s:%s で募集中だよ。", channel, nick, host, port));
	}

	/** QUITコマンドを送信します。 */
	private void postQuit(IRCMessage msg)
	{
		irc.quit("good bye.");
	}

	/** ホスト名に対応するIPアドレスを取得します。 */
	private String dnsLookup(String host)
	{
		try
		{
			InetAddress address = InetAddress.getByName(host);
			return address.getHostAddress();
		}
		catch (UnknownHostException x)
		{
			x.printStackTrace();
			return host;
		}
	}
}

スクリプト言語と比べて思ったこと

Java正規表現はString#matches()メソッドで提供されているんだけど、「\\d」とかエスケープシーケンスが重複すると混乱する。

  • フォーマット文字列に直接format()メソッドが書きたい

PHPみたいに変数をフォーマット文字列に含ませるのはやりすぎかと思うけど、せめてformatメソッドが書きたい。

普通は次のように書くところを...

postMessage(String.format("NOTICE %s :%s さんが %s:%s で募集中だよ。", channel, nick, host, port));

次のように書きたいと思った。

postMessage("NOTICE %s :%s さんが %s:%s で募集中だよ。".format(channel, nick, host, port));

ちなみに、PHPだとこうなる...

postMessage("NOTICE $channel :$nick さんが $host:$port で募集中だよ。");

引数を整理するだけなら、オーバーロードで可変引数付きのメソッドを宣言しておけば、

public void postMessage(String format, Object... args);

すっきりは書けるけど、これだとコンパイル時にフォーマット文字列のエラーが検出されないので却下。FindBugs++

postMessage("NOTICE %s :%s さんが %s で募集中だよ。", channel, nick, host, port);
  • リスト代入が欲しい

IRCプロトコルを扱うプログラムは、文字列を刻んで複数の変数に代入することが多いので、リスト代入が欲しいと思った。

String[] tmp = "nick!user@host".split("[!@]");
nick = tmp[0];
user = tmp[1];
host = tmp[2];

リスト代入を使って書くと次のようになる。

list(nick, user, host) = "nick!user@host".split("[!@]");

#java-jaが過疎ってるのでTwitterにRTしてみた!

まずは放置してたIRCライブラリを更新しました!
以前はメソッドの命名規約で分岐するようにしてたんですが、Java 5が普及したのでアノテーションがいいよね!ってことで@Replyアノテーションを導入したバージョンに生まれ変わりました!

アプリケーションのソースコードは次のとおりです。
rt-botが#java-jaに常駐して、みんなのメッセージをTwitterに流します。
逆にTwitterのタイムラインで#java-jaを含むメッセージが来たらIRCに流すほうが便利かな?意見求む!

/*
 *  Copyright (c) 2009 tarchan. All rights reserved.
 *  
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *      * Redistributions of source code must retain the above copyright
 *        notice, this list of conditions and the following disclaimer.
 *      * Redistributions in binary form must reproduce the above copyright
 *        notice, this list of conditions and the following disclaimer in the
 *        documentation and/or other materials provided with the distribution.
 *      * Neither the name of the tarchan nor the
 *        names of its contributors may be used to endorse or promote products
 *        derived from this software without specific prior written permission.
 *  
 *  THIS SOFTWARE IS PROVIDED BY tarchan ``AS IS'' AND ANY
 *  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 *  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 *  DISCLAIMED. IN NO EVENT SHALL tarchan BE LIABLE FOR ANY
 *  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.mac.tarchan.ircbox;

import java.io.IOException;

import twitter4j.Twitter;
import twitter4j.TwitterException;

import com.mac.tarchan.irc.client.IRCClient;
import com.mac.tarchan.irc.client.IRCMessage;
import com.mac.tarchan.irc.client.Reply;

/**
 * RT
 * 
 * @author tarchan
 */
public class RT
{
	/** IRC クライアント */
	private IRCClient irc;

	/** Twitter ID */
	private String twitterID;

	/** Twitter パスワード */
	private String twitterPassword;

	/**
	 * #java-ja の発言を Twitter に RT します。
	 * 
	 * @param args {Twitter ID} {Twitter Password}
	 */
	public static void main(String[] args)
	{
		try
		{
			new RT(args[0], args[1]);
		}
		catch (IOException x)
		{
			x.printStackTrace();
			System.exit(1);
		}
	}

	/**
	 * RTボットを起動します。
	 * 
	 * @param twitterID Twitter ID
	 * @param twitterPassword Twitter パスワード
	 * @throws IOException 入出力エラーが発生した場合
	 */
	public RT(String twitterID, String twitterPassword) throws IOException
	{
		// Twitter のアカウント情報
		this.twitterID = twitterID;
		this.twitterPassword = twitterPassword;

		// IRC のアカウント情報
		irc = new IRCClient();
		irc.addAllHandlers(this);
		irc.setProperty("irc.host", "irc.freenode.net");
		irc.setProperty("irc.port", "6667");
		irc.setProperty("irc.encoding", "UTF-8");
		irc.setProperty("irc.nick.name", "rt-bot");
		irc.setProperty("irc.real.name", "http://twitter.com/oauth_clients/details/12539");
		irc.open();
	}

	/**
	 * デバッグログを表示します。
	 * 
	 * @param msg IRCメッセージ
	 */
	@Reply("ALL")
	public void debug(IRCMessage msg)
	{
//		if (msg.isNumelicReply() && msg.getNumber() >= 400)
		{
			System.out.println(String.format("[%s] %s", msg.getCommand(), msg.getTrail()));
		}
	}

	/**
	 * #java-ja に JOIN した時にあいさつします。
	 * 
	 * @param msg IRCメッセージ
	 */
	@Reply("001")
	public void join(IRCMessage msg)
	{
		irc.postMessage(String.format("JOIN %s", "#java-ja"));
		msg("RT ボットを起動しました。");
	}

	/**
	 * メッセージを受け取ります。
	 * 
	 * @param msg IRCメッセージ
	 */
	@Reply("PRIVMSG")
	public void privmsg(IRCMessage msg)
	{
		rt(msg.getNick(), msg.getTrail());
	}

	/**
	 * Twitter に RT します。
	 * 
	 * <pre>
	 * ex. tarchan: RT @{nick}: {msg} #java-ja
	 * </pre>
	 * 
	 * @param nick ニックネーム
	 * @param str テキスト
	 */
	public void rt(String nick, String str)
	{
		String rt = String.format("RT @%s: %s #java-ja", nick, str);
		System.out.println(rt);
		try
		{
			Twitter twitter = new Twitter(twitterID, twitterPassword);
			twitter.updateStatus(rt);
		}
		catch (TwitterException x)
		{
			x.printStackTrace();
		}
	}

	/**
	 * IRC にメッセージを送信します。
	 * 
	 * @param str テキスト
	 */
	public void msg(String str)
	{
		irc.postMessage(String.format("NOTICE %s :%s", "#java-ja", str));
	}
}

以上!

Webで立ち読み

ねとらぼ:「やってよかった(涙)」 モーニング・ツー、Web無料公開で売り上げアップ - ITmedia ニュース

ネットで無料公開した雑誌の売り上げが好調みたいですね。
ネットで公開したら本が売れなくなるとかいって貝に閉じこもってる出版社が多いけど、だったら本屋で立ち読みするのはNGなのかよ!って話です。立ち読みできない本屋に存在価値ないです。指名買いするだけならAmazonで十分すぎる。

モーニング・ツーは、電子出版の老舗ボイジャーが提供するT-Time CrochetというWebブラウザ・プラグインをインストールして閲覧します。
このプラグインはMac OS X版もあります!この手のプラグインって独自形式のために、Windows専用だったりするけど、モーニングの編集部はMacに優しいね。
T-Timeの使用感はとても良くて、読みたい作品を選んだあとプラグインで全画面表示にすれば、カーソルキーだけで前後にページをめくりながら読むことに没頭できます。

ガンガンJOKERは、Digital Object ReaderというFlash製のプレイヤーで閲覧します。
Flashプラグインがインストールしてあれば追加のインストールはいらないんだろうけど、小さいウインドウが開くだけでダメな感じ。
そこで、RightZoomか、megazoomerをインストールすると、Safariのウインドウを全画面で表示できるのでいい感じに読めるようになりますよ。
Flash爆発しろ!