Twitterでフォローされているがしていない人を調べるプログラムをScalaで作成した

ねぇ君、Twitterやってる?

私はこの前からやってる。発言を強制されず、しかし誰かの発言に自由にコメントすることが可能で、今日のおかずも創作詩歌も企業からのお知らせも全部ごちゃまぜなとこが斬新だと思った。Twitterを始める前は、単に掲示板やmixiみたいなもんだと思っていたけど、まったく違うものだった。例えて言うなら社交ダンスと盆踊りくらい違う。

最初は有名人や企業のお知らせをフォローしていたんだけど、次第に何人かの人達をフォローしたり、フォローされたりするようになった。フォローしてくれる人はどこで私を発見するのだろう?かなりの謎だ。

フォローしている人の発言が並ぶところをタイムラインと呼ぶんだけど、フォローしている人が増えるに従ってタイムラインにたくさんのツイート(つぶやき)が並ぶようになり、そのうちに全部を読むのに時間がかかるようになってきた。すると、タイムラインを処理する能力の関係で、フォローしてもらってもフォローを返さない人というのが出てくる。つまり相手に読ませといてこっちは読んでないということだ。

有名人ならともかく、こんな路傍の石のような自分がこの仕打ちはどうなのよ?と思い、そんな人達をリストアップしてフォローすることにした。人類フォロー補完計画である。しかし手作業で調べるのは面倒だ!

そんなわけで「フォローされているがしていない人」を表示するプログラムを作成した。ついでに「フォローしているがされていない人」も表示するようにした。今回は練習のために初めてScalaを使ってみた。はじめてのScalaプログラミングである。なお、プログラムの手抜きのため、フォロワー(自分をフォローしている人)、フレンド(自分がフォローしている人)は最近の100人分しか取得していない。たくさんフォロワーがいる人には役立たなくてごめんなさい。

以下がプログラム。保存時のエンコーディングUTF-8

TwitterFollowers.scala

import scala.io.Source
import scala.xml.XML

object TwitterFollowers {
  def main(argv:Array[String]) {
    if (argv.length == 0) return
    
    val id = TwitterId(argv.first)
    val user = new TwitterUser(id)
    
    printNames("フォローしているが、されていない", user.friendsButNotFollowers)
    printNames("フォローされているが、していない", user.followersButNotFriends)
  }
  
  def printNames(title:String, names:TwitterNames) = {
    println(title)
    println(names.asString)
    println
  }
}

class TwitterUser(id:TwitterId) {
  val friends = getFriends
  val followers = getFollowers
  
  def friendsButNotFollowers = {
    friends - followers
  }
  
  def followersButNotFriends = {
    followers - friends
  }
  
  def getFriends = {
    val url = "http://api.twitter.com/1/statuses/friends/" +
      id.screenName + ".xml"
    TwitterNames(getNames(url))
  }
  
  def getFollowers = {
    val url = "http://api.twitter.com/1/statuses/followers/" +
      id.screenName + ".xml"
    TwitterNames(getNames(url))
  }
  
  def getNames(url:String) = {
    val xml = getXml(url)
    val names = (xml \\ "screen_name").map(_.text).toList
    names.sort((s1, s2) => (s1 compareTo s2) < 0)
  }
  
  def getXml(url:String) = {
    val src = Source.fromURL(url)
    XML.loadString(src.getLines.mkString)
  }
}

case class TwitterId(screenName:String)

case class TwitterNames(names:List[String]) {
  def -(other:TwitterNames) = {
    TwitterNames(names.filter(!other.contains(_)))
  }
  
  def asString = names match {
    case Nil => "";
    case _   => names.reduceLeft{_ + "\n" + _};
  }
  
  def contains(name:String) = names.contains(name)
}

コンパイルして、引数にユーザー名(“@”をつけて指定する名前。screen nameというらしい)を指定して実行すると「フォローしているがされていない人」と「フォローされているがしていない人」を表示する。以下の実行結果は一部伏字にしてある。

> scalac TwitterFollowers.scala
> scala TwitterFollowers pazworld

フォローしているが、されていない
FDMA_JAPAN
Hayabusa_JAXA
TwitBird
earthquake_jp
hashtags
tenkijp

フォローされているが、していない
808******
noj******
osa******
rak******
tam******
tou******
ww*******
yu*******

これを基にフォローを返せばいいんだ。よし、がんばろう!

ちなみにコンパイル用のRakefileはこちら。rakeのみでコンパイル、rake runで実行、rake clobberで後片付け。

require 'rake/clean'

CLOBBER.include('*.class')

SRCFILE = 'TwitterFollowers.scala'
CLASSFILE = 'TwitterFollowers.class'
USER = 'pazworld'

desc 'Compile.'
task :default => [CLASSFILE]

desc 'Run program.'
task :run => [CLASSFILE] do
  sh "scala TwitterFollowers #{USER}"
end

file CLASSFILE => [SRCFILE] do |t|
  sh "scalac #{t.prerequisites.join(' ')}"
end

Scalaと親和性の高いMavenを使わずにRakeを使っているのは、単に私がMavenの使い方を勉強していないから。サーセン(^^;)