( ꒪⌓꒪) ゆるよろ日記

( ゚∀゚)o彡°オパーイ!オパーイ! ( ;゚皿゚)ノシΣ フィンギィィーーッ!!!

ATNDでTwitter連携を設定している人のTwitterIDを抜き出すスクリプト書いたよ

ATNDがTwitterと連携できるようになりましたね。そこで、イベントの参加者のTwitterIDを抜き出すスクリプト書きましたよ。

object ATNDEventTwitterList {
  def main( args:Array[String] ) = {
    args.headOption.foreach { eventId =>
      ATNDApi.event( eventId ) foreach { event =>
        val users = event.users
        val tmax = users.map{ _.twitterId  }.flatten.map{ _.length }.max
        val fmt = "%6d : %s : %-" + tmax + "s : %s"

        println( event.title )
        users.map { u =>
          fmt format( u.userId, if( u.join ) "参加" else "補欠", u.twitterId.getOrElse("-"), u.nickname )
        } foreach { println }
      }
    }
  }
}


こんなの作っておいて、eventIdを渡すと...

ozaki@yuroyoro-MacBook $ scala ATNDEventTwitterList 5871                                                                           [~/sandbox/.../yuroyoro/atnd] 
Picked up _JAVA_OPTIONS: -Dfile.encoding=UTF-8
わかる!JavaVM ― 2時間でわかる?JavaVM入門
  4600 : 参加 : yuroyoro      : yuroyoro
  5038 : 参加 : -             : 神速
 32447 : 参加 : rinfield      : rinfield
 22669 : 参加 : -             : shinobu.aoki
 27977 : 参加 : matobat       : matobat
  6049 : 参加 : kazunori_279  : kazunori_279
  4945 : 参加 : -             : Yamashiro0217
  ...


こんな感じになるわけです。


ATNDのAPIでは、まだ参加者のTwitterIDを取得することができないので、ユーザーのページのHTMLからTwitterIDをクロールしてます。なので、イベントの参加者数だけリクエストが飛びます...。


これ使ってリマインダ送ったり、補欠から繰り上がったら通知するサービスとかできそうですね。ってかATNDがそのうち実装するか。


JVM勉強会の参加者にmentionsでリマインダ送ろうかと思ったけど、スパム報告されるかもしれないのでまだやってません。


ソースです


yuroyoro's
gist: 508039 — Gist

import java.util.Date
import java.text.SimpleDateFormat
import java.net.URL
import scala.xml._
import scala.xml.parsing.XhtmlParser
import scala.io.Source

case class User( userId:Int, nickname:String, twitterId:Option[String], join:Boolean = true )
class Event( eventId:String, eventXml:Node ) {
  lazy val usersXml = XML.load( new URL( "http://api.atnd.org/events/users/?event_id=%s" format eventId ))
  val sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'+09:00'")

  def value( tag:String ) = eventXml \ tag text
  def optionValue( tag:String ) = (eventXml \ tag ).headOption.map{ v => v text }.filter{ _.nonEmpty }

  def dateValue( tag:String ) = optionValue( tag ) map { d => sdf.parse(d) }
  def intValue( tag:String ) = optionValue(tag) map { _.toInt } getOrElse(0)
  def doubleValue( tag:String ) = optionValue(tag) map{ _.toDouble } getOrElse(0.0)

  val title:String = value("title")
  val catchs:String = value("catch")
  val description:String = value("description")
  val eventUrl:String = value("event-url")
  val startedAt:Option[Date] = dateValue("started-at")
  val endedAt:Option[Date] = dateValue("ended-at")
  val url:String = value("url")
  val limit:Int = intValue("limit")
  val address:String = value("address")
  val place:String = value("place")
  val lat:Double = doubleValue("lat")
  val lon:Double = doubleValue("lon")
  val ownerId:Int = intValue("owner-id")
  val ownerNickname:String = value("owner-nickname")
  val accepted:Int = intValue("accepted")
  val waiting:Int = intValue("waiting")
  val updatedAt:Option[Date] = dateValue("updated-at")

  lazy val users: Seq[User] = usersXml \\ "user" map { u =>
    val id = u \ "user-id" text
    val tid = ATNDApi.twitterId( id.toInt ) filter { _ != "-" }
    User( id.toInt,  u \ "nickname" text,  tid,  ( u \ "status" text ) == "1" )
  }

  lazy val joins = users.filter{ _.join }
  lazy val waitings = users.filter{ _.join == false }
}

object ATNDApi {
  private def loadEventXml( eventId:String ) =
    XML.load( new URL("http://api.atnd.org/events/?event_id=%s" format eventId )) \\ "event" headOption

  def event( eventId:String ):Option[Event] =
    loadEventXml( eventId ) map { xml => new Event( eventId, xml ) }

  def twitterId( id:Int):Option[String] = {
    XhtmlParser( Source.fromURL( new URL("http://atnd.org/users/%s" format id ) )) \\ "dl" filter { dl =>
      (dl \ "dt" text) == "Twitter ID"
    } filter { dl =>
      (dl \ "dt" text) == "Twitter ID"
    } map { dl => dl \ "dd" text } headOption
  }
}

object ATNDEventTwitterList {
  def main( args:Array[String] ) = {
    args.headOption.foreach { eventId =>
      ATNDApi.event( eventId ) foreach { event =>
        val users = event.users
        val tmax = users.map{ _.twitterId  }.flatten.map{ _.length }.max
        val fmt = "%6d : %s : %-" + tmax + "s : %s"

        println( event.title )
        users.map { u =>
          fmt format( u.userId, if( u.join ) "参加" else "補欠", u.twitterId.getOrElse("-"), u.nickname )
        } foreach { println }
      }
    }
  }
}