package com.nostra13.socialsharing.twitter.extpack.winterwell.jtwitter; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.AbstractList; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import com.nostra13.socialsharing.twitter.extpack.winterwell.json.JSONArray; import com.nostra13.socialsharing.twitter.extpack.winterwell.json.JSONException; import com.nostra13.socialsharing.twitter.extpack.winterwell.json.JSONObject; import com.nostra13.socialsharing.twitter.extpack.winterwell.jtwitter.Twitter.IHttpClient; /** * A Twitter list, which uses lazy-fetching of its members. * <p> * The methods of this object will call Twitter when they need to, and store the * results. E.g. the first call to {@link #size()} might require a call to * Twitter, but subsequent calls will not. * <p> * WARNING: Twitter only returns list members in batches of 20. So reading a * large list can be slow and use quite a few calls to Twitter. * <p> * To find out what lists you or another user has, see * {@link Twitter#getLists()} and {@link Twitter#getLists(String)}.<br> * To find out what lists you or another user are *in*, see * {@link Twitter#getListsContainingMe()} and * {@link Twitter#getListsContaining(String, boolean)}. * * @see Twitter * @author daniel * */ public class TwitterList extends AbstractList<User> { /** * A lazy-loading list viewer. This will fetch details from Twitter when you * call it's methods. This is for access to an existing list - it does NOT * create a new list on Twitter. * * @param ownerScreenName * * @param owner * .screenName The Twitter screen-name for the list's owner. * @param slug * The list's name. Technically the slug and the name needn't be * the same, but they usually are. * @param jtwit * a JTwitter object (this must be able to authenticate). * @throws Twitter.Exception.E404 * if the list does not exist */ public static TwitterList get(String ownerScreenName, String slug, Twitter jtwit) { return new TwitterList(ownerScreenName, slug, jtwit); } private boolean _private; /** * cursor for paging through the members of the list */ private long cursor = -1; private String description; /** * The same client as the JTwitter object used in the constructor. */ private final IHttpClient http; private Number id; private final Twitter jtwit; private int memberCount = -1; private String name; /** * never null (but may be a dummy object) */ private User owner; private String slug; private int subscriberCount; private final List<User> users = new ArrayList<User>(); /** * Used by {@link Twitter#getLists(String)} * * @param json * @param jtwit * @throws JSONException */ TwitterList(JSONObject json, Twitter jtwit) throws JSONException { this.jtwit = jtwit; this.http = jtwit.getHttpClient(); init2(json); } /** * A lazy-loading list viewer. This will fetch some details here, but the * list of members will be loaded from Twitter on demand (to minimise the * API calls). <b>This is for access to an existing list - it does NOT * create a new list on Twitter.</b> * * @see #TwitterList(String, Twitter, boolean, String) which creates new * lists. * * @param owner * .screenName The Twitter screen-name for the list's owner. * @param slug * The list's name. Technically the slug and the name needn't be * the same, but they usually are. * @param jtwit * a JTwitter object (this must be able to authenticate). * @throws Twitter.Exception.E404 * if the list does not exist * @deprecated Due to the potential for confusion with * {@link #TwitterList(String, Twitter, boolean, String)} Use * {@link #get(String, String, Twitter)} instead. */ @Deprecated public TwitterList(String ownerScreenName, String slug, Twitter jtwit) { assert ownerScreenName != null && slug != null && jtwit != null; this.jtwit = jtwit; this.owner = new User(ownerScreenName); // use a dummy here this.name = slug; this.slug = slug; this.http = jtwit.getHttpClient(); init(); } /** * <b>CREATE</b> a brand new Twitter list. Accounts are limited to 20 lists. * * @see #TwitterList(String, String, Twitter) which views existing lists. * * @param listName * The list's name. * @param jtwit * a JTwitter object (this must be able to authenticate). * @param description * A description for this list. Can be null. */ public TwitterList(String listName, Twitter jtwit, boolean isPublic, String description) { assert listName != null && jtwit != null; this.jtwit = jtwit; String ownerScreenName = jtwit.getScreenName(); assert ownerScreenName != null; this.name = listName; this.slug = listName; this.http = jtwit.getHttpClient(); // create! String url = jtwit.TWITTER_URL + "/lists/create.json"; Map<String, String> vars = InternalUtils.asMap("name", listName, "mode", isPublic ? "public" : "private", "description", description); String json = http.post(url, vars, true); try { JSONObject jobj = new JSONObject(json); init2(jobj); } catch (JSONException e) { throw new TwitterException("Could not parse response: " + e); } } /** * Add a user to the list. List size is limited to 500 users. * * @return testing for membership could be slow, so this is usually true. */ @Override public boolean add(User user) { if (users.contains(user)) return false; String url = jtwit.TWITTER_URL + "/lists/members/create.json"; Map map = getListVars(); map.put("screen_name", user.screenName); String json = http.post(url, map, true); // adjust size try { JSONObject jobj = new JSONObject(json); memberCount = jobj.getInt("member_count"); // update local users.add(user); return true; } catch (JSONException e) { throw new TwitterException("Could not parse response: " + e); } } @Override public boolean addAll(Collection<? extends User> newUsers) { List newUsersList = new ArrayList(newUsers); newUsersList.removeAll(users); if (newUsersList.size() == 0) return false; String url = jtwit.TWITTER_URL + "/lists/members/create_all.json"; Map map = getListVars(); int batchSize = 100; for (int i = 0; i < users.size(); i += batchSize) { int last = i + batchSize; String names = InternalUtils.join(newUsersList, i, last); map.put("screen_name", names); String json = http.post(url, map, true); // adjust size try { JSONObject jobj = new JSONObject(json); memberCount = jobj.getInt("member_count"); } catch (JSONException e) { throw new TwitterException("Could not parse response: " + e); } } // error messages? return true; } /** * Delete this list! * * @throws TwitterException * on failure */ public void delete() { try { String URL = jtwit.TWITTER_URL + "/" + owner.screenName + "/lists/" + URLEncoder.encode(slug, "utf-8") + ".json?_method=DELETE"; http.post(URL, null, http.canAuthenticate()); } catch (UnsupportedEncodingException e) { throw new TwitterException(e); } } @Override public User get(int index) { // pull from Twitter as needed String url = jtwit.TWITTER_URL + "/lists/members.json"; Map<String, String> vars = getListVars(); while (users.size() < index + 1 && cursor != 0) { vars.put("cursor", Long.toString(cursor)); String json = http.getPage(url, vars, true); try { JSONObject jobj = new JSONObject(json); JSONArray jarr = (JSONArray) jobj.get("users"); List<User> users1page = User.getUsers(jarr.toString()); users.addAll(users1page); cursor = new Long(jobj.getString("next_cursor")); } catch (JSONException e) { throw new TwitterException("Could not parse user list" + e); } } return users.get(index); } public String getDescription() { init(); return description; } /** * @return vars identifying the list in question */ private Map<String, String> getListVars() { Map vars = new HashMap(); if (id != null) { vars.put("list_id", id); return vars; } vars.put("owner_screen_name", owner.screenName); vars.put("slug", slug); return vars; } public String getName() { return name; } public User getOwner() { return owner; } /** * Returns a list of statuses from this list. * * @return List<Status> a list of Status objects for the list * @throws TwitterException */ // Added TG 3/31/10 public List<Status> getStatuses() throws TwitterException { try { String jsonListStatuses = http.getPage( jtwit.TWITTER_URL + "/" + owner.screenName + "/lists/" + URLEncoder.encode(slug, "UTF-8") + "/statuses.json", null, http.canAuthenticate()); List<Status> msgs = Status.getStatuses(jsonListStatuses); return msgs; } catch (UnsupportedEncodingException e) { throw new TwitterException(e); } } public int getSubscriberCount() { init(); return subscriberCount; } /** * @return users who follow this list. Currently this is just the first 20 * users. TODO cursor support for more than 20 users. */ public List<User> getSubscribers() { String url = jtwit.TWITTER_URL + "/lists/subscribers.json"; Map<String, String> vars = getListVars(); String json = http.getPage(url, vars, true); try { JSONObject jobj = new JSONObject(json); JSONArray jsonUsers = jobj.getJSONArray("users"); return User.getUsers2(jsonUsers); } catch (JSONException e) { throw new TwitterException("Could not parse response: " + e); } } private String idOrSlug() { // TODO encode slugs here? return id != null ? id.toString() : slug; } /** * Fetch list info from Twitter */ private void init() { if (memberCount != -1) return; String url = jtwit.TWITTER_URL + "/lists/show.json"; Map<String, String> vars = getListVars(); String json = http.getPage(url, vars, true); try { JSONObject jobj = new JSONObject(json); init2(jobj); } catch (JSONException e) { throw new TwitterException("Could not parse response: " + e); } } private void init2(JSONObject jobj) throws JSONException { // owner.screenName = ; memberCount = jobj.getInt("member_count"); subscriberCount = jobj.getInt("subscriber_count"); name = jobj.getString("name"); slug = jobj.getString("slug"); id = jobj.getLong("id"); _private = "private".equals(jobj.optString("mode")); description = jobj.optString("description"); JSONObject user = jobj.getJSONObject("user"); owner = new User(user, null); } public boolean isPrivate() { init(); return _private; } /** * Remove a user from the list. * * @return testing for membership could be slow, so this is always true. */ @Override public boolean remove(Object o) { try { User user = (User) o; String url = jtwit.TWITTER_URL + "/lists/members/destroy.json"; Map map = getListVars(); map.put("screen_name", user.screenName); String json = http.post(url, map, true); // adjust size JSONObject jobj = new JSONObject(json); memberCount = jobj.getInt("member_count"); // update local users.remove(user); return true; } catch (JSONException e) { throw new TwitterException("Could not parse response: " + e); } } public void setDescription(String description) { String url = jtwit.TWITTER_URL + "/lists/update.json"; Map<String, String> vars = getListVars(); vars.put("description", description); String json = http.getPage(url, vars, true); try { JSONObject jobj = new JSONObject(json); init2(jobj); } catch (JSONException e) { throw new TwitterException("Could not parse response: " + e); } } public void setPrivate(boolean isPrivate) { String url = jtwit.TWITTER_URL + "/lists/update.json"; Map<String, String> vars = getListVars(); vars.put("mode", isPrivate ? "private" : "public"); String json = http.getPage(url, vars, true); try { JSONObject jobj = new JSONObject(json); init2(jobj); } catch (JSONException e) { throw new TwitterException("Could not parse response: " + e); } } @Override public int size() { init(); return memberCount; } @Override public String toString() { return getClass().getSimpleName() + "[" + owner + "." + name + "]"; } }