/** * Use cursors to fetch upto jtwit.maxResults TODO More controlled paging?? * * @param url API method to call * @param screenName * @param userId * @return twitter-id numbers for friends/followers of screenName or userId Is affected by {@link * #maxResults} */ private List<Number> getUserIDs(String url, String screenName, Long userId) { Long cursor = -1L; List<Number> ids = new ArrayList<Number>(); if (screenName != null && userId != null) throw new IllegalArgumentException( "cannot use both screen_name and user_id when fetching user_ids"); Map<String, String> vars = InternalUtils.asMap("screen_name", screenName, "user_id", userId); while (cursor != 0 && !jtwit.enoughResults(ids)) { vars.put("cursor", String.valueOf(cursor)); String json = http.getPage(url, vars, http.canAuthenticate()); try { // it seems Twitter will occasionally return a raw array JSONArray jarr; if (json.charAt(0) == '[') { jarr = new JSONArray(json); cursor = 0L; } else { JSONObject jobj = new JSONObject(json); jarr = (JSONArray) jobj.get("ids"); cursor = new Long(jobj.getString("next_cursor")); } for (int i = 0; i < jarr.length(); i++) { ids.add(jarr.getLong(i)); } if (jarr.length() == 0) { // No more break; } } catch (JSONException e) { throw new TwitterException.Parsing(json, e); } } return ids; }
/** * Returns information of a given user, specified by user-id. * * @param userId The user-id of a user. * @throws exception if the user does not exist - or has been terminated (as happens to spam * bots). */ public User show(Number userId) { Map<String, String> vars = InternalUtils.asMap("user_id", userId.toString()); String json = http.getPage(jtwit.TWITTER_URL + "/users/show.json", vars, http.canAuthenticate()); try { User user = new User(new JSONObject(json), null); return user; } catch (JSONException e) { throw new TwitterException.Parsing(json, e); } }
/** * @return true if followerScreenName <i>is</i> following followedScreenName * @throws TwitterException.E403 if one of the users has protected their updates and you don't * have access. This can be counter-intuitive (and annoying) at times! Also throws E403 if one * of the users has been suspended (we use the {@link SuspendedUser} exception sub-class for * this). * @throws TwitterException.E404 if one of the users does not exist */ public boolean isFollower(String followerScreenName, String followedScreenName) { assert followerScreenName != null && followedScreenName != null; try { Map vars = InternalUtils.asMap( "source_screen_name", followerScreenName, "target_screen_name", followedScreenName); String page = http.getPage(jtwit.TWITTER_URL + "/friendships/show.json", vars, http.canAuthenticate()); JSONObject jo = new JSONObject(page); JSONObject trgt = jo.getJSONObject("relationship").getJSONObject("target"); boolean fby = trgt.getBoolean("followed_by"); return fby; } catch (TwitterException.E403 e) { if (e instanceof SuspendedUser) throw e; // Should this be a suspended user exception instead? // Let's ask Twitter // TODO check rate limits - only do if we have spare capacity String whoFirst = followedScreenName.equals(jtwit.getScreenName()) ? followerScreenName : followedScreenName; try { // this could throw a SuspendedUser exception show(whoFirst); String whoSecond = whoFirst.equals(followedScreenName) ? followerScreenName : followedScreenName; if (whoSecond.equals(jtwit.getScreenName())) throw e; show(whoSecond); } catch (TwitterException.RateLimit e2) { // ignore } // both shows worked? throw e; } catch (TwitterException e) { // FIXME investigating a weird new bug if (e.getMessage() != null && e.getMessage().contains("Two user ids or screen_names must be supplied")) throw new TwitterException( "WTF? inputs: follower=" + followerScreenName + ", followed=" + followedScreenName + ", call-by=" + jtwit.getScreenName() + "; " + e.getMessage()); throw e; } }
/** * Common backend for {@link #bulkShow(List)} and {@link #bulkShowById(List)}. Works in batches of * 100. * * <p>This will throw exceptions from the 1st page of results, but swallow them from subsequent * pages (which are likely to be rate limit errors). * * <p>Suspended bot accounts seem to just get ignored. * * @param stringOrNumber * @param screenNamesOrIds */ List<User> bulkShow2(String apiMethod, Class stringOrNumber, Collection screenNamesOrIds) { // Requires authentication in v1.1, though not in 1 which is still usable boolean auth = InternalUtils.authoriseIn11(jtwit); int batchSize = 100; ArrayList<User> users = new ArrayList<User>(screenNamesOrIds.size()); List _screenNamesOrIds = screenNamesOrIds instanceof List ? (List) screenNamesOrIds : new ArrayList(screenNamesOrIds); for (int i = 0; i < _screenNamesOrIds.size(); i += batchSize) { int last = i + batchSize; String names = InternalUtils.join(_screenNamesOrIds, i, last); String var = stringOrNumber == String.class ? "screen_name" : "user_id"; Map<String, String> vars = InternalUtils.asMap(var, names); try { String json = http.getPage(jtwit.TWITTER_URL + apiMethod, vars, auth); List<User> usersi = User.getUsers(json); users.addAll(usersi); } catch (TwitterException.E404 e) { // All names were bogus or deleted users! // Oh well } catch (TwitterException e) { // Stop here. // Don't normally throw an exception so we don't waste the // results we have. if (users.size() == 0) throw e; e.printStackTrace(); break; } } return users; }
/** * Start following a user. * * @param username Required. The ID or screen name of the user to befriend. * @return The befriended user, or null if (a) they were already being followed, or (b) they * protect their tweets & you already requested to follow them. * @throws TwitterException if the user does not exist or has been suspended. * @see #stopFollowing(String) */ public User follow(String username) throws TwitterException { if (username == null) throw new NullPointerException(); if (username.equals(jtwit.getScreenName())) throw new IllegalArgumentException("follow yourself makes no sense"); String page = null; try { Map<String, String> vars = InternalUtils.asMap("screen_name", username); page = http.post(jtwit.TWITTER_URL + "/friendships/create.json", vars, true); // is this needed? doesn't seem to fix things // http.getPage(jtwit.TWITTER_URL+"/friends", null, true); return new User(new JSONObject(page), null); } catch (SuspendedUser e) { throw e; } catch (TwitterException.Repetition e) { return null; } catch (E403 e) { // check if we've tried to follow someone we're already following try { if (isFollowing(username)) return null; } catch (TwitterException e2) { // no extra info then } throw e; } catch (JSONException e) { throw new TwitterException.Parsing(page, e); } }
/** * Low-level method for fetching e.g. your friends * * @param url * @param screenName e.g. your screen-name * @return */ private List<User> getUsers(String url, String screenName) { Map<String, String> vars = InternalUtils.asMap("screen_name", screenName); List<User> users = new ArrayList<User>(); Long cursor = -1L; while (cursor != 0 && !jtwit.enoughResults(users)) { vars.put("cursor", cursor.toString()); JSONObject jobj; try { jobj = new JSONObject(http.getPage(url, vars, http.canAuthenticate())); users.addAll(User.getUsers(jobj.getString("users"))); cursor = new Long(jobj.getString("next_cursor")); } catch (JSONException e) { throw new TwitterException.Parsing(null, e); } } return users; }
/** * Enables notifications for updates from the specified user <i>who must already be a friend</i>. * * @param username Get notifications from this user, who must already be one of your friends. * @return the specified user */ public User notify(String username) { Map<String, String> vars = InternalUtils.asMap("screen_name", username); String page = http.getPage(jtwit.TWITTER_URL + "/notifications/follow.json", vars, true); try { return new User(new JSONObject(page), null); } catch (JSONException e) { throw new TwitterException.Parsing(page, e); } }
/** * blocks/destroy: Un-blocks screenName for the authenticating user. Returns the un-blocked user * when successful. If relationships existed before the block was instated, they will not be * restored. * * @param screenName * @return the now un-blocked User * @see #block(String) */ public User unblock(String screenName) { HashMap vars = new HashMap(); vars.put("screen_name", screenName); // Returns if the authenticating user is blocking a target user. // Will return the blocked user's object if a block exists, and error // with // a HTTP 404 response code otherwise. String json = http.post(jtwit.TWITTER_URL + "/blocks/destroy.json", vars, true); return InternalUtils.user(json); }
/** * @param truncatedStatus If this is a twitlonger.com truncated status, then call twitlonger to * fetch the full text. * @return the full status message. If this is not a twitlonger status, this will just return the * status text as-is. * @see #updateLongStatus(String, long) */ public String getLongStatus(Status truncatedStatus) { // regex for http://tl.gd/ID int i = truncatedStatus.text.indexOf("http://tl.gd/"); if (i == -1) return truncatedStatus.text; String id = truncatedStatus.text.substring(i + 13).trim(); String response = http.getPage("http://www.twitlonger.com/api_read/" + id, null, false); Matcher m = contentTag.matcher(response); boolean ok = m.find(); if (!ok) throw new TwitterException.TwitLongerException("TwitLonger call failed", response); String longMsg = m.group(1).trim(); return longMsg; }
/** * Returns information of a given user, specified by screen name. * * @param screenName The screen name of a user. * @throws exception if the user does not exist * @throws SuspendedUser if the user has been terminated (as happens to spam bots). * @see #show(long) */ public User show(String screenName) throws TwitterException, TwitterException.SuspendedUser { Map vars = InternalUtils.asMap("screen_name", screenName); // Test Code Debugger at work - expected closures until 2012 String json = ""; try { json = http.getPage(jtwit.TWITTER_URL + "/users/show.json", vars, http.canAuthenticate()); } catch (Exception e) { // we get here? throw new TwitterException.E404( "User " + screenName + " does not seem to exist, their user account may have been removed from the service"); } // Debuggers no longer at work if (json.length() == 0) throw new TwitterException.E404(screenName + " does not seem to exist"); try { User user = new User(new JSONObject(json), null); return user; } catch (JSONException e) { throw new TwitterException.Parsing(json, e); } }
/** * @param screenName * @param device Can be null (for do not change) * @param retweets Can be null (for do not change) * @return User object for screenName. This does not hold much info, and it can have bogus * relationship (follower/following) info (bugs seen March 2013). */ public User setNotifications(String screenName, Boolean device, Boolean retweets) { if (device == null && retweets == null) { return null; // no-op } Map<String, String> vars = InternalUtils.asMap("screen_name", screenName, "device", device, "retweets", retweets); String page = http.post(jtwit.TWITTER_URL + "/friendships/update.json", vars, true); try { JSONObject jo = new JSONObject(page).getJSONObject("relationship").getJSONObject("target"); return new User(jo, null); } catch (JSONException e) { throw new TwitterException.Parsing(page, e); } }
public boolean isBlocked(String screenName) { try { HashMap vars = new HashMap(); vars.put("screen_name", screenName); // Returns if the authenticating user is blocking a target user. // Will return the blocked user's object if a block exists, and // error with // a HTTP 404 response code otherwise. String json = http.getPage(jtwit.TWITTER_URL + "/blocks/exists.json", vars, true); return true; } catch (TwitterException.E404 e) { return false; } }
/** * @return an array of numeric user ids the authenticating user is blocking. Use {@link * #showById(Collection)} if you want to convert thse into User objects. */ public List<Number> getBlockedIds() { String json = http.getPage(jtwit.TWITTER_URL + "/blocks/ids.json", null, true); try { JSONArray arr = json.startsWith("[") ? new JSONArray(json) : new JSONObject(json).getJSONArray("ids"); List<Number> ids = new ArrayList(arr.length()); for (int i = 0, n = arr.length(); i < n; i++) { ids.add(arr.getLong(i)); } return ids; } catch (JSONException e) { throw new TwitterException.Parsing(json, e); } }
/** * Variant of {@link #searchUsers(String)} which gives access to later pages. Note: You can only * access upto the first 1000 matching results (a Twitter limitation -- c.f. * https://dev.twitter.com/docs/api/1.1/get/users/search). * * @param searchTerm * @param page Which page to retrieve (the first page is 1) * @return */ public List<User> searchUsers(String searchTerm, int page) { assert searchTerm != null; Map<String, String> vars = InternalUtils.asMap("q", searchTerm); // provide paging if (page > 1) { vars.put("page", Integer.toString(page)); } if (jtwit.count != null && jtwit.count < 20) { vars.put("per_page", String.valueOf(jtwit.count)); } // yes, it requires authentication String json = http.getPage(jtwit.TWITTER_URL + "/users/search.json", vars, true); List<User> users = User.getUsers(json); return users; }
/** * Use twitlonger.com to post a lengthy tweet. See twitlonger.com for more details on their * service. * * @param message Must be > 140 chars * @param inReplyToStatusId Can be null if this isn't a reply * @return A Twitter status using a truncated message with a link to twitlonger.com */ public Status updateLongStatus(String message, Number inReplyToStatusId) { assert twitlongerApiKey != null : "Wrong constructor used -- you must supply an api-key to post"; if (message.length() < 141) { throw new IllegalArgumentException( "Message too short (" + inReplyToStatusId + " chars). Just post a normal Twitter status. "); } String url = "http://www.twitlonger.com/api_post"; Map<String, String> vars = InternalUtils.asMap( "application", twitlongerAppName, "api_key", twitlongerApiKey, "username", jtwit.getScreenName(), "message", message); if (inReplyToStatusId != null && inReplyToStatusId.doubleValue() != 0) { vars.put("in_reply", inReplyToStatusId.toString()); } // ?? set direct_message 0/1 as appropriate if allowing long DMs String response = http.post(url, vars, false); Matcher m = contentTag.matcher(response); boolean ok = m.find(); if (!ok) { throw new TwitterException.TwitLongerException("TwitLonger call failed", response); } String shortMsg = m.group(1).trim(); // Post to Twitter Status s = jtwit.updateStatus(shortMsg, inReplyToStatusId); m = idTag.matcher(response); ok = m.find(); if (!ok) { // weird - but oh well return s; } String id = m.group(1); // Once a message has been successfully posted to Twitlonger and // Twitter, it would be really useful to send back the Twitter ID for // the message. This will allow users to manage their Twitlonger posts // and delete not only the Twitlonger post, but also the Twitter post // associated with it. It will also makes replies much more effective. try { url = "http://www.twitlonger.com/api_set_id"; vars.remove("message"); vars.remove("in_reply"); vars.remove("username"); vars.put("message_id", "" + id); vars.put("twitter_id", "" + s.getId()); http.post(url, vars, false); } catch (Exception e) { // oh well } // done return s; }