-
Notifications
You must be signed in to change notification settings - Fork 150
Expand file tree
/
Copy pathTwitterClient.scala
More file actions
118 lines (95 loc) · 4.7 KB
/
Copy pathTwitterClient.scala
File metadata and controls
118 lines (95 loc) · 4.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package actors
import akka.actor.SupervisorStrategy.Restart
import akka.actor._
import akka.event.Logging
import akka.actor.OneForOneStrategy
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import play.api.libs.iteratee.Iteratee
import play.api.libs.ws.WS
import play.api.libs.oauth.{RequestToken, ConsumerKey, OAuthCalculator}
import play.api.libs.json.{JsError, JsSuccess, Json}
import org.joda.time.DateTime
import scala.concurrent.duration._
import models._
import birdwatchUtils._
import models.TweetImplicits._
/** Actors related to image processing */
object TwitterClient {
/** OAuth consumer key and secret for Twitter Streaming API */
val consumerKey = ConsumerKey(Conf.get("twitter.consumer.key"), Conf.get("twitter.consumer.secret"))
/** OAuth request key and secret for Twitter Streaming API */
val accessToken = RequestToken(Conf.get("twitter.accessToken.key"), Conf.get("twitter.accessToken.secret"))
def stripImageUrl(t: Tweet) = t.copy(profile_image_url = t.profile_image_url.replaceAll("http://", "").replaceAll("_normal", ""))
/** Protocol for Twitter Client actors */
case class AddTopic(topic: String)
case class RemoveTopic(topic: String)
case object StartListening
case object CheckStatus
case object RestartListening
var lastTweetReceived: DateTime = new DateTime(0L)
val topics: scala.collection.mutable.HashSet[String] = new scala.collection.mutable.HashSet[String]()
/** Iteratee for processing each chunk from Twitter stream of Tweets. Parses Json chunks
* as Tweet instances and publishes them to eventStream. */
val tweetIteratee = Iteratee.foreach[Array[Byte]] {
chunk =>
lastTweetReceived = DateTime.now()
val chunkString = new String(chunk, "UTF-8")
val json = Json.parse(chunkString)
/** persist any valid JSON from Twitter Streaming API */
Tweet.insertJson(json)
Tweet.count.map(c => println("Tweets: " + c))
TweetReads.reads(json) match {
case JsSuccess(t: Tweet, _) => {
ActorStage.imgSupervisor ! WordCount.wordsChars(stripImageUrl(t))
}
case JsError(msg) => println(msg)
}
}
class Supervisor(eventStream: akka.event.EventStream) extends Actor with ActorLogging {
override val supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1.minute) {
case _: Exception => Restart
}
override val log = Logging(context.system, this)
override def preStart() { println("TwitterClient Supervisor starting") }
override def preRestart(reason: Throwable, message: Option[Any]) {
log.error(reason, "Restarting due to [{}] when processing [{}]", reason.getMessage, message.getOrElse(""))
}
val twitterClient = context.actorOf(Props(new TwitterClient()), "TwitterClient")
/** Checking status of Twitter Streaming API connection every 5 seconds */
context.system.scheduler.schedule(30 seconds, 30 seconds, self, CheckStatus )
/** Receives control messages for starting / restarting supervised client and adding or removing topics */
def receive = {
case StartListening => twitterClient ! StartListening
case RestartListening => twitterClient ! Kill
case AddTopic(topic) => println("Topics: " + topics); topics.add(topic)
case RemoveTopic(topic) => println("Topics: " + topics); topics.remove(topic)
case CheckStatus => {
if ((DateTime.now.getMillis - lastTweetReceived.getMillis) > 15000) {
twitterClient ! Kill
twitterClient ! StartListening
}
}
}
/** Image retrieval actor, receives Tweets, retrieves the Twitter profile images for each user and passes them on to
* conversion actor. */
class TwitterClient() extends Actor with ActorLogging {
override val log = Logging(context.system, this)
override def preStart() { println("Starting TwitterClient actor for topics: " + topics) }
override def preRestart(reason: Throwable, message: Option[Any]) {
log.error(reason, "Restarting due to [{}] when processing [{}]", reason.getMessage, message.getOrElse(""))
}
val url = "https://stream.twitter.com/1.1/statuses/filter.json?track="
val conn = WS.url(url + TwitterClient.topics.mkString("%2C").replace(" ", "%20"))
.withTimeout(-1)
.sign(OAuthCalculator(consumerKey, accessToken))
.get(_ => TwitterClient.tweetIteratee)
/** Connects to Twitter Streaming API and retrieve a stream of Tweets for the specified search word or words.
* Passes received chunks of data into tweetIteratee */
def receive = {
case StartListening => {
println("Starting WS connection to Twitter")
}
}
}
}
}