package com.nostra13.socialsharing.twitter.extpack.winterwell.jtwitter;
import java.net.HttpURLConnection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Connect to the streaming API.
* <p>
* Duplicate messages may be delivered when reconnecting to the Streaming API.
*
* @author Daniel
*/
public class TwitterStream extends AStream {
public static enum KMethod {
/**
* Follow hashtags, users or regions
*/
filter,
/** Everything! Requires special access privileges! */
firehose,
/** Requires special access privileges! */
links,
/**
* New-style retweets. Requires special access privileges! From
* dev.twitter.com: <i>Few applications require this level of access.
* Creative use of a combination of other resources and various access
* levels can satisfy nearly every application use case.</i>
* */
retweet,
/**
* Spritzer or Garden-hose: A sample of tweets, suitable for trend
* analysis. <br>
* The default level (spritzer) is roughly 1% of all public tweets. <br>
* The upgraded level (garden-hose - apply to Twitter for this) is 10%. <br>
* In both cases the algorithm is based on the tweet-id modulo 100.
*/
sample
}
/**
* Used to help avoid breaking api limits.
*/
private static Map<String, AStream> user2stream = new ConcurrentHashMap();
private List<Long> follow;
private List<double[]> locns;
KMethod method = KMethod.sample;
private List<String> track;
/**
*
* @param client
* This will have it's timeout set to 90 seconds. So you probably
* don't want to reuse the object with the REST api.
*/
public TwitterStream(Twitter jtwit) {
super(jtwit);
}
@Override
HttpURLConnection connect2() throws Exception {
connect3_rateLimit();
String url = "https://stream.twitter.com/1/statuses/" + method
+ ".json";
Map<String, String> vars = new HashMap();
vars.put("delimited", "length");
if (follow != null && follow.size() != 0) {
vars.put("follow", InternalUtils.join(follow, 0, Integer.MAX_VALUE));
}
if (track != null && track.size() != 0) {
vars.put("track", InternalUtils.join(track, 0, Integer.MAX_VALUE));
}
// use post in case it's a long set of vars
HttpURLConnection con = client.post2_connect(url, vars);
return con;
}
/**
* Protect the rate limits & _help_ you avoid annoying Twitter (only
* locally! And forgetful! Do NOT rely on this)
*/
private void connect3_rateLimit() {
if (jtwit.getScreenName() == null)
return; // dunno
AStream s = user2stream.get(jtwit.getScreenName());
if (s != null && s.isConnected())
throw new TwitterException.TooManyLogins(
"One account, one stream (running: "
+ s
+ "; trying to run"
+ this
+ ").\n But streams OR their filter parameters, so one stream can do a lot.");
// memory paranoia
if (user2stream.size() > 1000) {
user2stream = new ConcurrentHashMap<String, AStream>();
}
user2stream.put(jtwit.getScreenName(), this);
}
@Override
void fillInOutages2(Twitter jtwit2, Outage outage) {
if (method != KMethod.filter)
throw new UnsupportedOperationException();
// keywords?
if (track != null) {
for (String keyword : track) {
List<Status> msgs = jtwit.search(keyword);
for (Status status : msgs) {
if (tweets.contains(status)) {
continue;
}
tweets.add(status);
}
}
}
// users?
if (follow != null) {
for (Long user : follow) {
List<Status> msgs = jtwit.getUserTimeline(user);
for (Status status : msgs) {
if (tweets.contains(status)) {
continue;
}
tweets.add(status);
}
}
}
// regions?
if (locns != null && ! locns.isEmpty())
throw new UnsupportedOperationException("TODO"); // TODO
}
public List<String> getTrackKeywords() {
return track;
}
/**
* @param userIds Upto 5,000 userids to follow
*/
public void setFollowUsers(List<Long> userIds) {
method = KMethod.filter;
// if (userIds!=null&&! userIds.isEmpty()){
follow = userIds;
// }
// If not, really don't! it screws stuff up.
}
/**
* TODO This is not implemented yet!
* 25 0.1-360 degree location boxes.
*
* Only tweets that are both created using the Geotagging API and are placed
* from within a tracked bounding box will be included in the stream – the
* user’s location field is not used to filter tweets
*
* @param boundingBoxes
* Each element consists of longitude/latitude south-west,
* north-east.
*/
@Deprecated // TODO
public void setLocation(List<double[]> boundingBoxes) {
method = KMethod.filter;
this.locns = boundingBoxes;
throw new RuntimeException("TODO! Not implemented yet (sorry)");
}
/**
* Set the method. The default is "sample", as this is the only one which
* works with no extra settings.
*
* @param method
*/
void setMethod(KMethod method) {
this.method = method;
}
/**
* See https://dev.twitter.com/docs/streaming-api/methods#track
* <p>
* Terms are exact-matched, and also exact-matched ignoring punctuation.
* Each term may be up to 60 characters long.
* <p>
* Exact matching on phrases, that is, keywords with spaces,
* is not supported. Keywords containing punctuation will only exact match
* tokens and, other than keywords prefixed by # and @, will tend to
* never match. Non-space separated languages, such as CJK and
* Arabic, are currently unsupported as tokenization only occurs on
* whitespace and punctuation. Other UTF-8 phrases should exact match
* correctly, but will not substitute similar characters to their
* least-common-denominator. For all these cases, consider falling back
* to the Search REST API.
*
* @param keywords
* The default access level allows up to 400 track keywords.
* You can also do phrases, separating words with a space.
*
*/
public void setTrackKeywords(List<String> keywords) {
// check them for length
for (String kw : keywords) {
if (kw.length() > 60) {
throw new IllegalArgumentException("Track term too long: "+kw+" (60 char limit)");
}
}
// we don't check >400 'cos you might have special access
this.track = keywords;
method = KMethod.filter;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("TwitterStream");
sb.append("[" + method);
if (track != null) {
sb.append(" track:" + InternalUtils.join(track, 0, 5));
}
if (follow != null) {
sb.append(" follow:" + InternalUtils.join(follow, 0, 5));
}
if (locns != null) {
sb.append(" in:" + InternalUtils.join(locns, 0, 5));
}
sb.append("]");
return sb.toString();
}
/**
* default: false
* If true, json is only sent to listeners, and polling based access
* via {@link #getTweets()} will return no results.
* @see #addListener(IListen)
*/
public void setListenersOnly(boolean listenersOnly) {
this.listenersOnly = listenersOnly;
}
}