読者です 読者をやめる 読者になる 読者になる

Twitter検索用 IRC Bot

Scala

$ scala -version

2.9.0.RC1

使用ライブラリ

PircBot 1.5.0
http://www.jibble.org/pircbot.php

HatoChanBot.scala

import scala.xml._
import java.io._
import org.jibble.pircbot._

object HatoChanBot {

  def main(args: Array[String]) {
    val bot = new HatoChan
    bot.init
    bot.setAlreadyPosted(load(Conf.DAT_FILE_NAME))
    while (true) {
      save(Conf.DAT_FILE_NAME, bot.crawl)
      Thread.sleep(Conf.CRAWL_INTERVAL)
    }
  }
  // deserialize
  def load(fileName: String): List[String] = {
    try {
      val dat = new ObjectInputStream(new FileInputStream(fileName))
      val posted = dat.readObject.asInstanceOf[List[String]]
      dat.close
      return posted
    } catch {
      case _ => Nil
    }
  }
  // serialize
  def save(fileName: String, posted: List[String]) {
    val dat = new ObjectOutputStream(new FileOutputStream(fileName))
    dat.writeObject(posted)
    dat.close
  }
}

class HatoChanBot(var alreadyPosted: List[String] = Nil) extends PircBot {

  def init() {
    this.setName(Conf.IRC_NICK)
    this.setLogin(Conf.IRC_NICK)
    this.setVersion(Conf.IRC_MAIL)
    this.setMessageDelay(Conf.IRC_MSG_DELAY);
    this.setEncoding(Conf.IRC_ENCODING)
    this.connect(Conf.IRC_HOST, Conf.IRC_PORT)
    this.joinChannel(Conf.IRC_CHANNEL)
  }

  def crawl(): List[String] = {
    val src = io.Source.fromURL(Conf.URL, Conf.IRC_ENCODING).getLines.mkString
    val xml = XML.loadString(src)
    val entries = xml \\ "entry"
    for (entry <- entries) {
      val twitterId = "@" + (entry \ "author" \ "name").text.replaceAll("""\s\(.*\)""", "")
      val content = (entry \ "content").text.replaceAll("""<.*?>""", "")
      val msg = twitterId + ": " + content
      if (alreadyPosted.contains(msg) == false) {
        this.sendNotice(Conf.IRC_CHANNEL, msg)
        this.alreadyPosted :::= List(msg)
      }
    }
    return this.alreadyPosted
  }
  def setAlreadyPosted(posted: List[String]) { this.alreadyPosted = posted }
}

IRCにSend済みの発言Listをシリアライズして保持&チェックすることで、IRCで同じ内容の発言をすることを防いでます。
クロール対象は http://search.twitter.com/ の検索結果ページの XML-Feed。
XMLの解析には id:yuroyoro さんの下の記事を参考にしました。

ScalaでWebAPIをたたいてXMLを処理するための定型パターンのまとめ 〜ゆろよろ日記
http://d.hatena.ne.jp/yuroyoro/20091027/1256611681

Conf.scala

object Conf{
  val URL = "http://search.twitter.com/search.atom?q=iPad2"
  val DAT_FILE_NAME = "posted.dat"
  val CRAWL_INTERVAL = 1000 * 60 * 3 // 逮捕されないように常識的な値を設定

  val IRC_NICK = "hato_chan"
  val IRC_MAIL = "hato_chan@mail.poppo.jp"
  val IRC_HOST = "foo.bar.ne.jp"
  val IRC_PORT = 6660
  val IRC_CHANNEL = "#hatochan"
  val IRC_MSG_DELAY = 5000 // IRCサーバにkickされないように常識ry
  val IRC_ENCODING = "UTF-8"
}

設定用シングルトン
(後で普通のテキストの設定ファイルにします…)