// BlogBridge -- RSS feed reader, manager, and web based service
// Copyright (C) 2002-2006 by R. Pito Salas
//
// This program is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free Software Foundation;
// either version 2 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with this program;
// if not, write to the Free Software Foundation, Inc., 59 Temple Place,
// Suite 330, Boston, MA 02111-1307 USA
//
// Contact: R. Pito Salas
// mailto:pitosalas@users.sourceforge.net
// More information: about BlogBridge
// http://www.blogbridge.com
// http://sourceforge.net/projects/blogbridge
//
// $Id$
//
package com.salas.bb.twitter;
import com.salas.bb.core.GlobalController;
import com.salas.bb.utils.StringUtils;
import com.salas.bb.utils.net.BBHttpClient;
import oauth.signpost.OAuthConsumer;
import oauth.signpost.exception.OAuthException;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Twitter gateway.
*/
public class TwitterGateway
{
private static final Logger LOG = Logger.getLogger(TwitterGateway.class.getName());
// Screen name extraction pattern
public static final Pattern PATTERN_SCREEN_NAME =
Pattern.compile("http://(www\\.)?twitter\\.com/([^/\\?#\\s]+)($|#|\\?)");
private static final Pattern PATTERN_HASHTAG =
Pattern.compile("^http://search\\.twitter\\.com/search(\\.(json|atom)?)?\\?q=(#|%23)([^&\\s\\+]+)($|&)");
/**
* Posts an update to the twitter account.
*
* @param status status message.
*
* @throws IOException if fails to communicate.
* @throws OAuthException OAuth error.
*/
public static void update(String status)
throws IOException, OAuthException
{
reply(status, null);
}
/**
* Posts a reply to the twitter account.
*
* @param status status message.
* @param replyToId ID of the original message or NULL.
*
* @throws IOException if fails to communicate.
* @throws OAuthException OAuth error.
*/
public static void reply(String status, String replyToId)
throws IOException, OAuthException
{
URL url = new URL("http://api.twitter.com/1/statuses/update.json");
Map<String, String> data = new HashMap<String, String>();
data.put("status", status);
//data.put("source", "blogbridge");
if (replyToId != null) data.put("in_reply_to_status_id", replyToId);
post(url, data);
}
/**
* Returns TRUE if the current user has friendship with the given screenname.
*
* @param screenname user to check friendship with.
*
* @return TRUE if follows.
*
* @throws IOException if fails to communicate.
*/
public static boolean isFollowing(String screenname)
throws IOException
{
TwitterPreferences prefs = getPreferences();
String userA = encode(prefs.getScreenName());
String userB = encode(screenname);
URL url = new URL("http://twitter.com/friendships/exists.json?user_a=" + userA + "&user_b=" + userB);
String res;
try
{
res = get(url);
} catch (OAuthException e)
{
throw new IOException(e.getMessage(), e.getCause());
}
return res != null && res.contains("true");
}
/**
* Requests to follow the given user.
*
* @param screenName user.
*
* @throws IOException if fails to communicate.
* @throws OAuthException OAuth error.
*/
public static void follow(String screenName)
throws IOException, OAuthException
{
screenName = encode(screenName);
URL url = new URL("http://twitter.com/friendships/create/" + screenName + ".json");
Map<String, String> data = new HashMap<String, String>();
data.put("follow", "true");
post(url, data);
}
/**
* Requests to unfollow the given user.
*
* @param screenName user.
*
* @throws IOException if fails to communicate.
* @throws OAuthException OAuth error.
*/
public static void unfollow(String screenName)
throws IOException, OAuthException
{
screenName = encode(screenName);
URL url = new URL("http://twitter.com/friendships/destroy/" + screenName + ".json");
post(url, null);
}
/**
* Returns HTML for the user info popup.
*
* @param screenName screen name.
*
* @return HTML.
*
* @throws IOException if fails to communicate.
*/
public static String userInfoHTML(String screenName)
throws IOException
{
URL url = new URL("http://twitter.com/users/show/" + screenName + ".json");
String response;
try
{
response = get(url);
} catch (OAuthException e)
{
throw new IOException(e.getMessage(), e.getCause());
}
String html;
try
{
JSONObject data = new JSONObject(response);
String description = data.getString("description");
String name = data.getString("name");
String profile_image_url = data.getString("profile_image_url");
String screen_name = data.getString("screen_name");
String location = data.getString("location");
String followers = data.getString("followers_count");
String updates = data.getString("statuses_count");
String friends = data.getString("friends_count");
html = "<html><div style='padding:10px'>";
html += "<table border='0'>";
html += "<tr valign='top'>";
if (isShowingPics())
{
html += "<td><img src='" + profile_image_url + "'></td>";
html += "<td width='10'> </td>";
}
html += "<td width='300'>";
html += "<p><strong>" + name + "</strong> (" + screen_name + ")</p>";
if (StringUtils.isNotEmpty(location)) html += "<p>" + location + "</p>";
if (StringUtils.isNotEmpty(description)) html += "<br><p>" + description + "</p><br>";
html += "<p>Followers: " + followers + "</p>";
html += "<p>Friends: " + friends + "</p>";
html += "<p>Updates: " + updates + "</p>";
html += "</td></tr></table>";
html += "</div>";
} catch (JSONException e)
{
LOG.log(Level.INFO, "Failed to load screen name info", e);
html = null;
}
return html;
}
/**
* Search.
*
* @param query query.
*
* @return Formatted HTML.
*
* @throws IOException if communication fails.
*/
public static String search(String query)
throws IOException
{
URL url = new URL("http://search.twitter.com/search.json?q=" + encode(query) + "&rpp=5");
String response = BBHttpClient.get(url);
String html;
try
{
JSONObject data = new JSONObject(response);
JSONArray results = data.getJSONArray("results");
html = "<html><div style='padding:10px'>";
html += "<table border='0'>";
int count = results.length();
for (int i = 0; i < count; i++)
{
JSONObject record = results.getJSONObject(i);
html += "<tr valign='top'>";
if (isShowingPics())
{
html += "<td><img src='" + record.getString("profile_image_url") + "'></td>";
html += "<td width='10'> </td>";
}
html += "<td width='300'>";
html += "<p><strong>" + record.getString("from_user") + "</strong>: " + record.getString("text") + "</p>";
html += "</td>";
html += "</tr>";
}
html += "</table></div>";
} catch (JSONException e)
{
LOG.log(Level.INFO, "Failed to load search results for '" + query + "'", e);
html = null;
}
return html;
}
/**
* Returns preferences.
*
* @return preferences.
*/
private static TwitterPreferences getPreferences()
{
return GlobalController.SINGLETON.getModel().getUserPreferences().getTwitterPreferences();
}
/**
* Authenticated GET request.
*
* @param url URL.
*
* @return response.
*
* @throws IOException if communication fails.
* @throws OAuthException OAuth exception.
*/
private static String get(URL url)
throws IOException, OAuthException
{
TwitterPreferences prefs = getPreferences();
OAuthConsumer consumer = prefs.getConsumer();
return BBHttpClient.get(url, consumer);
}
/**
* Authenticated POST request.
*
* @param url URL.
* @param data data map.
*
* @throws IOException if communication fails.
* @throws OAuthException OAuth error.
*/
private static void post(URL url, Map<String, String> data)
throws IOException, OAuthException
{
TwitterPreferences prefs = getPreferences();
BBHttpClient.post(url, data, prefs.getConsumer());
}
/**
* URL-encodes the string.
*
* @param str string.
*
* @return string.
*
* @throws UnsupportedEncodingException if UTF-8 isn't supported -- nonsense.
*/
private static String encode(String str)
throws UnsupportedEncodingException
{
return URLEncoder.encode(str, "UTF-8");
}
/**
* Returns the username from the URL or NULL.
*
* @param url URL to analyze.
*
* @return screen name.
*/
public static String urlToScreenName(URL url)
{
if (url == null) return null;
String name = null;
String urls = url.toString();
Matcher m = PATTERN_SCREEN_NAME.matcher(urls);
try
{
if (m.find()) name = URLDecoder.decode(m.group(2), "UTF-8");
} catch (UnsupportedEncodingException e)
{
// Failed transformation -- ignore
}
return name;
}
/**
* Extracts the hashtag from the search URL.
*
* @param url URL.
*
* @return hashtag.
*/
public static String urlToHashtag(URL url)
{
if (url == null) return null;
String tag = null;
String urls = url.toString();
Matcher m = PATTERN_HASHTAG.matcher(urls);
try
{
if (m.find()) tag = URLDecoder.decode(m.group(4), "UTF-8");
} catch (UnsupportedEncodingException e)
{
// Failed transformation -- ignore
}
return tag;
}
/**
* Returns TRUE if profile pics loading is enabled.
*
* @return TRUE if profile pics loading is enabled.
*/
private static boolean isShowingPics()
{
return getPreferences().isProfilePics();
}
/**
* Returns the contents of the friends timeline RSS feed.
*
* @return RSS feed.
*
* @throws IOException in case of communication error.
* @throws OAuthException in case of authentication error.
*/
public static String friendsTimeline()
throws IOException, OAuthException
{
return get(new URL("http://api.twitter.com/1/statuses/friends_timeline.rss"));
}
}