StripBot
<time datetime=2011-08-01T01:36:17>1. 8. 2011</time> — k47 (CC by-nc-sa)
StripBot je jednoduchý robot, který sleduje RSSka několika online stripů a komiksů a na všechny novinky upozorňuje na Twitteru.
StripBota jsem původně napsal v březnu 2009 v Groovy, po víc než roce jsem ho přepsal do Javy a konečně před několika měsíci ho přepsal do Scaly.
Tady jsou kompletní zdrojáky:
build.sbt
scalaVersion := "2.9.0-1" libraryDependencies += "commons-codec" % "commons-codec" % "1.4" libraryDependencies += "oauth.signpost" % "signpost-core" % "1.2.1.1" libraryDependencies += "org.jdom" % "jdom" % "1.1" libraryDependencies += "rome" % "rome" % "0.9" libraryDependencies += "jtwitter" % "jtwitter" % "1.8.3" from "http://www.winterwell.com/software/jtwitter/jtwitter.jar"
stripbot.scala
// https://twitter.com/stripbot package stripBot import scala.actors.Actor import scala.actors.Actor._ import java.net.{ URL, URLEncoder, MalformedURLException } import java.io.{ FileReader, BufferedReader, FileWriter, IOException } import java.util.Date import com.sun.syndication.feed.synd.SyndEntry import com.sun.syndication.io.{ SyndFeedInput, XmlReader } import winterwell.jtwitter.{ Twitter, OAuthSignpostClient } object Main extends App { val tweeter = new Tweeter("stripbot", Config.oauthKey, Config.oauthSecret) val rssFetcher = new Fetcher(Config.sources, tweeter) rssFetcher.start tweeter.start } object Config { val sources = Seq( StripSource("Bugemos", "http://bugemos.com/?q=rss.xml"), StripSource("balónek strip", "http://lubosbranda.blog.idnes.cz/rss/"), StripSource("bezejmenný hrdina", "http://picasaweb.google.com/data/feed/base/user/marusjakub/albumid/5110845406354432785?alt=rss&kind=photo&hl=cs"), StripSource("ITBiz", "http://www.itbiz.cz/taxonomy/term/25/0/feed"), StripSource("iDNES komix", "http://servis.idnes.cz/rss.asp?c=komiksy"), StripSource("XKCD", "http://xkcd.com/rss.xml", "EN"), StripSource("Questionable Content", "http://www.questionablecontent.net/QCRSS.xml", "EN"), StripSource("Johny Wander", "http://www.johnnywander.com/feed", "EN"), StripSource("Wasted Talent", "http://feeds2.feedburner.com/WastedTalentRss", "EN"), StripSource("chainsawsuit", "http://feeds.feedburner.com/Chainsawsuit", "EN"), StripSource("Cyanide & happyiness", "http://feeds.feedburner.com/Explosm", "EN", { e => e.getTitle.matches("""\d+\.\d+.\d+""") }) ) val oauthKey = "---YOUR-OAUTH-KEY---" val oauthSecret = "---YOUR-OAUTH-SECRET---" } // --- main actors class Fetcher(sources: Seq[StripSource], tweeter: Tweeter) extends Actor { val lastCheckFile = "lastCheck.txt" def checkRss(source: StripSource, lastCheck: Date) = for { e <- Util.readRssFromUrl(source.rssUrl) if source.filterFn(e) if e.getPublishedDate != null && e.getPublishedDate.after(lastCheck) } yield e def formatMessage(source: StripSource, entry: SyndEntry) = source.name+" - "+entry.getTitle.trim+" "+Util.shortenUrl(entry.getLink)+" "+source.lang+" #stripy" def getLastCheck: Date = { var line = Util readLine lastCheckFile if (line == null || line == "") new Date() else new Date(line.toLong) } def setLastCheck = Util.writeLine(lastCheckFile, "" + new Date().getTime) def act { println(sources.mkString("\n")) loop { var lastCheck = getLastCheck println("Last check: " + lastCheck) for { source <- sources entry <- checkRss(source, lastCheck) } tweeter ! formatMessage(source, entry) setLastCheck Thread.sleep(1000 * 60 * 60) // 1 hour } } } class Tweeter(username: String, oauthKey: String, oauthSecret: String) extends Actor { val accessTokensFile = "accessTokens.txt" def connectToTwitter = { val tokens = Util readLine accessTokensFile val oauthClient = if (tokens.nonEmpty) { val t = tokens.split(" ") new OAuthSignpostClient(oauthKey, oauthSecret, t(0), t(1)) } else { val oauthClient = new OAuthSignpostClient(oauthKey, oauthSecret, "oob") oauthClient.authorizeDesktop() val v = OAuthSignpostClient.askUser("Please enter the verification PIN from Twitter") oauthClient.setAuthorizationCode(v) Util.writeLine(accessTokensFile, oauthClient.getAccessToken.mkString(" ")) oauthClient } new Twitter(username, oauthClient) } def act { val twitter = connectToTwitter loop { react { case m => twitter.updateStatus(m.toString) println("tweeted: " + m.toString) } } } } // ---- case class StripSource (name: String, rssUrl: String, lang: String = "CZ", filterFn: SyndEntry => Boolean = x => true) object Util { def readLine(f: String) = io.Source.fromFile(f).getLines.next def writeLine(f: String, data: String) = { val fw = new FileWriter(f) fw write data fw.close() } def readRssFromUrl(url: String): List[SyndEntry] = { try { val i = new SyndFeedInput().build(new XmlReader(new URL(url))).getEntries List(i.toArray(new Array[SyndEntry](0)) : _*) } catch { case _ => List() } } def shortenUrl(url: String) = { val line = io.Source.fromURL("https://url.k47.cz/api/get/?url=" + URLEncoder.encode(url, "UTF8")).getLines.next if (line startsWith "ERROR") throw new MalformedURLException line } }