Twitter上でIokeプログラミングをとりあえず的に

@IokeGround fib = method(n, if(n == 0, 0, if(n == 1, 1, fib(n-1) + fib(n-2)))). fib(10)
http://twitter.com/katzchang/status/2450789054

とすると、

@katzchang 55
http://twitter.com/IokeGround/status/2450791565

こう返す。

つまり、Twitterで@メッセージを送ると、Iokeメッセージとして解釈してごりごり処理してくれるわけだ。超楽しいww

ちなみに、フッタをつけてる場合は";"を最後に入れると良い感じ。thanks id:bleis-tift

@IokeGround fact = method(n, if(n == 0, 1, n * fact(n - 1))). fact(5); 文法わからないけどfibを参考に投げてみる [SQL Puzzlersやります! http://bit.ly/M3zRO]
http://twitter.com/bleis/status/2451082449

@bleis 120
http://twitter.com/IokeGround/status/2451131902

今後の課題として:

  • 手動起動する必要があるのはあんまりなので、起動してると定時でreplyを取りにいって適当に処理してくれるとうれしいとか。
  • エラーがあるとIoke自体が落ちちゃうけどそれってどうなのとか。
  • 実行権限をuserでしぼってるけどそれって大丈夫なのとか。
  • stax.netにのせられる?
    • とすると、セキュリティ的な心配ってそんなに必要ない?
      • でも、無限ループとかされたらどうすんだろか?

以下、自分用めも。

package iokebot;

import java.io.*;
import java.util.*;
import java.util.logging.Logger;

import twitter4j.*;
import ioke.lang.*;
import ioke.lang.exceptions.*;

public class IokeBot {
	final MyRuntime r;
	final TwitterHandler h;
	final Prop p;
	
	final Logger logger;
	
	public IokeBot() throws Exception, ControlFlow {
		this.p = new Prop();
		this.r = new MyRuntime();
		this.h = new TwitterHandler();
		this.logger = Logger.getLogger(this.getClass().getName());
		
		logger.info("user:"+p.name);
		logger.info("commando:"+p.commando);
		logger.info("last status id:"+p.lastStatusID);
	}
	
	public void check() throws Exception, ControlFlow {
		Status status = this.h.checkStatuses();
		logger.info("status:" + status);
		if (status != null) {
			Object result = this.h.handleMessage(status);
			logger.info("result:" + result);
			long newStatusID = this.h.updateLastStatusID(status);
			logger.info("newStatusID:" + newStatusID);
		}
	}
	
	public void tearDown() throws ControlFlow.Exit {
		this.r.tearDown();
		logger.info("Bye...");
	}
	
	class Result {
		Status status;
		Object result;
	}
	
	class TwitterHandler {
		final MyTwitter t;
		final String commando;
		
		TwitterHandler() {
			this.t = new MyTwitter(p.name, p.password);
			this.commando = p.commando;
		}
		
		Object handleMessage(Status status) throws TwitterException, ControlFlow {
			if(validCommando(status)) {
				String message = status.getText().substring(status.getText().indexOf(" "));
				Object res = r.eval(message);
				t.updateStatus("@" + status.getUser().getName() + " " + res.toString(),
						status.getId());
				return res;
			} else {
				return "invalid commando.";
			}
		}
		
		long updateLastStatusID(Status status) throws IOException {
			p.updateLastStatusID(status.getId());
			return status.getId();
		}
		
		Status checkStatuses() throws TwitterException, ControlFlow, IOException {
			List<Status> statuses = t.getReplies(p.lastStatusID);
			logger.fine("statuses" + statuses.toString());
			if(!statuses.isEmpty()) {
				return statuses.get(0);
			}
			return null;
		}
		
		boolean validCommando(Status status) {
			return this.commando.equals(status.getUser().getName());
		}
	}
	
	static class Prop {
		final Properties p;
		static String filename = "src/iokebot/iokebot.properties.xml";
		final String name;
		final String password;
		final String commando;
		long lastStatusID;
		
		Prop() throws IOException {
			this.p = new Properties();
			
			InputStream is = new FileInputStream(filename);
			try {
				p.loadFromXML(is);
				this.name = p.getProperty("name");
				this.password = p.getProperty("password");
				this.commando = p.getProperty("commando");
				this.lastStatusID = Long.valueOf(p.getProperty("last_status_id"));
			} finally {
				is.close();
			}
		}
		
		void updateLastStatusID(long id) throws IOException {
			this.lastStatusID = id;
			
			OutputStream os = new FileOutputStream(filename);
			try {
				p.setProperty("last_status_id", String.valueOf(id));
				p.storeToXML(os, "IokeBot Properties");
			} finally {
				os.close();
			}
		}
	}	
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IokeBot Properties</comment>
<entry key="last_status_id">2450789054</entry>
<entry key="name">IokeGround</entry>
<entry key="password"></entry>
<entry key="commando">katzchang</entry>
</properties>
package ioke.lang;

import ioke.lang.exceptions.ControlFlow;

public class MyRuntime {
	final Runtime r;
	
	public MyRuntime() throws Exception, ControlFlow {
		this.r = new Runtime();
		this.init();
	}
	
	public void init() throws ControlFlow {
		this.r.init();
		this.eval("Hi =\"Hi. see also: http://d.hatena.ne.jp/katzchang/20090703/p3\"");
	}
	
	public void tearDown() throws ControlFlow.Exit {
		this.r.tearDown();
	}
	
	public Object eval(String message) throws ControlFlow {
		return this.r.evaluateString(message);
	}
}
package twitter4j;

import java.util.List;

public class MyTwitter extends Twitter {
    private static final long serialVersionUID = -4218983212013279106L;
    
    public MyTwitter(String id, String password) {
    	super(id, password);
    }

    public List<Status> getReplies() throws TwitterException {
        return Status.constructStatuses(get(getBaseURL() + "statuses/replies.xml"
                , null, true), this);
    }
    
    public List<Status> getReplies(long sinceID) throws TwitterException {
        return Status.constructStatuses(get(getBaseURL() + "statuses/replies.xml"
                , null, new Paging(sinceID), true), this);
    }
}