2010년 12월 20일 월요일

Java 로 Facebook 어플리케이션 제작하기

아, 이렇게 훌륭한 일을 해서 남을 위해
인프라를 만들어 놓는 애들도 잇엇군요...!

http://t-machine.org/index.php/2007/08/02/how-to-make-facebook-apps-using-java-part-1/
http://t-machine.org/index.php/2007/08/13/how-to-make-facebook-apps-using-java-part-2/


FacebookRestClient.java

/*   +---------------------------------------------------------------------------+   | Facebook Development Platform Java Client                                 |   +---------------------------------------------------------------------------+   | Copyright (c) 2007 Facebook, Inc.                                         |   | All rights reserved.                                                      |   |                                                                           |   | Redistribution and use in source and binary forms, with or without        |   | modification, are permitted provided that the following conditions        |   | are met:                                                                  |   |                                                                           |   | 1. Redistributions of source code must retain the above copyright         |   |    notice, this list of conditions and the following disclaimer.          |   | 2. Redistributions in binary form must reproduce the above copyright      |   |    notice, this list of conditions and the following disclaimer in the    |   |    documentation and/or other materials provided with the distribution.   |   |                                                                           |   | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR      |   | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |   | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.   |   | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,          |   | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT  |   | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |   | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY     |   | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT       |   | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF  |   | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.         |   +---------------------------------------------------------------------------+   | For help with this library, contact developers-help@facebook.com          |   +---------------------------------------------------------------------------+  */  package com.facebook.api;  import java.io.BufferedInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.ProtocolException; import java.net.URL; import java.net.URLConnection; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set;  import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory;  import org.json.JSONException; import org.json.JSONStringer; import org.json.JSONWriter;  import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList;   public class FacebookRestClient {   public static final String TARGET_API_VERSION = "1.0";   public static final String ERROR_TAG = "error_response";   public static final String FB_SERVER = "api.facebook.com/restserver.php";    public static final String SERVER_ADDR = "http://" + FB_SERVER;   public static final String HTTPS_SERVER_ADDR = "https://" + FB_SERVER;   public static URL SERVER_URL = null;   public static URL HTTPS_SERVER_URL = null;   static {     try {       SERVER_URL = new URL(SERVER_ADDR);       HTTPS_SERVER_URL = new URL(HTTPS_SERVER_ADDR);     }     catch (MalformedURLException e) {       System.err.println("MalformedURLException: " + e.getMessage());       System.exit(1);     }   }    private final String _secret;   private final String _apiKey;   private final URL _serverUrl;    private String _sessionKey; // filled in when session is established   private boolean _isDesktop = false;   private String _sessionSecret; // only used for desktop apps   private int _userId = -1;    public static int NUM_AUTOAPPENDED_PARAMS = 5;   private static boolean DEBUG = false;   private Boolean _debug = null;    private File _uploadFile = null;    protected class Pair {     public N first;     public V second;      public Pair(N name, V value) {       this.first = name;       this.second = value;     }   }     public FacebookRestClient(String apiKey, String secret) {     this(SERVER_URL, apiKey, secret, null);   }    public FacebookRestClient(String apiKey, String secret, String sessionKey) {     this(SERVER_URL, apiKey, secret, sessionKey);   }    public FacebookRestClient(String serverAddr, String apiKey, String secret,                             String sessionKey) throws MalformedURLException {     this(new URL(serverAddr), apiKey, secret, sessionKey);   }    public FacebookRestClient(URL serverUrl, String apiKey, String secret, String sessionKey) {     _sessionKey = sessionKey;     _apiKey = apiKey;     _secret = secret;     _serverUrl = (null != serverUrl) ? serverUrl : SERVER_URL;   }    public static void setDebugAll(boolean isDebug) {     FacebookRestClient.DEBUG = isDebug;   }    public void setDebug(boolean isDebug) {     _debug = isDebug;   }    public boolean isDebug() {     return (null == _debug) ? FacebookRestClient.DEBUG : _debug.booleanValue();   }    public boolean isDesktop() {     return this._isDesktop;   }    public void setIsDesktop(boolean isDesktop) {     this._isDesktop = isDesktop;   }    /**    * Prints out the DOM tree.    */   public static void printDom(Node n, String prefix) {     String outString = prefix;     if (n.getNodeType() == Node.TEXT_NODE) {       outString += "'" + n.getTextContent().trim() + "'";     }     else {       outString += n.getNodeName();     }     System.out.println(outString);     NodeList children = n.getChildNodes();     int length = children.getLength();     for (int i = 0; i < href="http://jcs.mobile-utopia.com/jcs/s/FacebookRestClient" style="font-family: courier, verdana, arial, sans-serif; font-size: 13px; color: purple; text-decoration: none; font-weight: bold; ">FacebookRestClient.printDom(children.item(i), prefix + "  ");     }   }    private static CharSequence delimit(Collection iterable) {     // could add a thread-safe version that uses StringBuffer as well     if (iterable == null || iterable.isEmpty())       return null;      StringBuilder buffer = new StringBuilder();     boolean notFirst = false;     for (Object item: iterable) {       if (notFirst)         buffer.append(",");       else         notFirst = true;       buffer.append(item.toString());     }     return buffer;   }    protected static CharSequence delimit(Collection<Map.Entry<String, CharSequence>> entries,                                         CharSequence delimiter, CharSequence equals,                                         boolean doEncode) {     if (entries == null || entries.isEmpty())       return null;      StringBuilder buffer = new StringBuilder();     boolean notFirst = false;     for (Map.Entry<String, CharSequence> entry: entries) {       if (notFirst)         buffer.append(delimiter);       else         notFirst = true;       CharSequence value = entry.getValue();       buffer.append(entry.getKey()).append(equals).append(doEncode ? encode(value) : value);     }     return buffer;   }    /**    * Call the specified method, with the given parameters, and return a DOM tree with the results.    *    * @param method the fieldName of the method    * @param paramPairs a list of arguments to the method    * @throws Exception with a description of any errors given to us by the server.    */   protected Document callMethod(FacebookMethod method,                                 Pair<String, CharSequence>... paramPairs) throws FacebookException,                                                                                  IOException {     return callMethod(method, Arrays.asList(paramPairs));   }    /**    * Call the specified method, with the given parameters, and return a DOM tree with the results.    *    * @param method the fieldName of the method    * @param paramPairs a list of arguments to the method    * @throws Exception with a description of any errors given to us by the server.    */   protected Document callMethod(FacebookMethod method,                                 Collection<Pair<String, CharSequence>> paramPairs) throws FacebookException,                                                                                           IOException {     HashMap<String, CharSequence> params =       new HashMap<String, CharSequence>(2 * method.numTotalParams());      params.put("method", method.methodName());     params.put("api_key", _apiKey);     params.put("v", TARGET_API_VERSION);     if (method.requiresSession()) {       params.put("call_id", Long.toString(System.currentTimeMillis()));       params.put("session_key", _sessionKey);     }     CharSequence oldVal;     for (Pair<String, CharSequence> p: paramPairs) {       oldVal = params.put(p.first, p.second);       if (oldVal != null)         System.err.printf("For parameter %s, overwrote old value %s with new value %s.", p.first,                           oldVal, p.second);     }      assert (!params.containsKey("sig"));     String signature = generateSignature(FacebookSignatureUtil.convert(params.entrySet()), method.requiresSession());     params.put("sig", signature);      try {       DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();       boolean doHttps = this.isDesktop() && FacebookMethod.AUTH_GET_SESSION.equals(method);       InputStream data =         method.takesFile() ? postFileRequest(method.methodName(), params) : postRequest(method.methodName(),                                                                                         params,                                                                                         doHttps,                                                                                         true);       Document doc = builder.parse(data);       doc.normalizeDocument();       stripEmptyTextNodes(doc);        if (isDebug())         FacebookRestClient.printDom(doc, method.methodName() + "| "); // TEST       NodeList errors = doc.getElementsByTagName(ERROR_TAG);       if (errors.getLength() > 0) {         int errorCode =           Integer.parseInt(errors.item(0).getFirstChild().getFirstChild().getTextContent());         String message = errors.item(0).getFirstChild().getNextSibling().getTextContent();         throw new FacebookException(errorCode, message);       }       return doc;     }     catch (javax.xml.parsers.ParserConfigurationException ex) {       System.err.println("huh?" + ex);     }     catch (org.xml.sax.SAXException ex) {       throw new IOException("error parsing xml");     }     return null;   }    /**    * Hack...since DOM reads newlines as textnodes we want to strip out those    * nodes to make it easier to use the tree.    */   private static void stripEmptyTextNodes(Node n) {     NodeList children = n.getChildNodes();     int length = children.getLength();     for (int i = 0; i < href="http://jcs.mobile-utopia.com/jcs/s/Node" style="font-family: courier, verdana, arial, sans-serif; font-size: 13px; color: purple; text-decoration: none; font-weight: bold; ">Node c = children.item(i);       if (!c.hasChildNodes() && c.getNodeType() == Node.TEXT_NODE &&           c.getTextContent().trim().length() == 0) {         n.removeChild(c);         i--;         length--;         children = n.getChildNodes();       }       else {         stripEmptyTextNodes(c);       }     }   }    private String generateSignature(List<String> params, boolean requiresSession) {     String secret = (isDesktop() && requiresSession) ? this._sessionSecret : this._secret;     return FacebookSignatureUtil.generateSignature(params, secret);   }    private static String encode(CharSequence target) {     String result = target.toString();     try {       result = URLEncoder.encode(result, "UTF8");     }     catch (UnsupportedEncodingException e) {       System.err.printf("Unsuccessful attempt to encode '%s' into UTF8", result);     }     return result;   }    private InputStream postRequest(CharSequence method, Map<String, CharSequence> params,                                   boolean doHttps, boolean doEncode) throws IOException {     CharSequence buffer = (null == params) ? "" : delimit(params.entrySet(), "&", "=", doEncode);     URL serverUrl = (doHttps) ? HTTPS_SERVER_URL : _serverUrl;     if (isDebug()) {       System.out.print(method);       System.out.print(" POST: ");       System.out.print(serverUrl.toString());       System.out.print("/");       System.out.println(buffer);       System.out.flush();     }      HttpURLConnection conn = (HttpURLConnection) serverUrl.openConnection();     try {       conn.setRequestMethod("POST");     }     catch (ProtocolException ex) {       System.err.println("huh?" + ex);     }     conn.setDoOutput(true);     conn.connect();     conn.getOutputStream().write(buffer.toString().getBytes());      return conn.getInputStream();   }    /**    * Sets the FBML for a user's profile, including the content for both the profile box     * and the profile actions.     * @param userId - the user whose profile FBML to set    * @param fbmlMarkup - refer to the FBML documentation for a description of the markup and its role in various contexts     * @return a boolean indicating whether the FBML was successfully set    */   public boolean profile_setFBML(CharSequence fbmlMarkup, Integer userId) throws FacebookException, IOException {        return extractBoolean(this.callMethod(FacebookMethod.PROFILE_SET_FBML,                           new Pair<String, CharSequence>("uid", Integer.toString(userId)),                           new Pair<String, CharSequence>("markup", fbmlMarkup)));    }    /**    * Gets the FBML for a user's profile, including the content for both the profile box     * and the profile actions.     * @param userId - the user whose profile FBML to set    * @return a Document containing FBML markup    */   public Document profile_getFBML(Integer userId) throws FacebookException, IOException {     return this.callMethod(FacebookMethod.PROFILE_GET_FBML,                           new Pair<String, CharSequence>("uid", Integer.toString(userId)));    }    /**    * Recaches the referenced url.    * @param url string representing the URL to refresh    * @return boolean indicating whether the refresh succeeded    */   public boolean fbml_refreshRefUrl(String url) throws FacebookException, IOException {     return fbml_refreshRefUrl(new URL(url));   }    /**    * Recaches the referenced url.    * @param url the URL to refresh    * @return boolean indicating whether the refresh succeeded    */   public boolean fbml_refreshRefUrl(URL url) throws FacebookException, IOException {     return extractBoolean(this.callMethod(FacebookMethod.FBML_REFRESH_REF_URL,                                           new Pair<String, CharSequence>("url", url.toString())));   }    /**    * Recaches the image with the specified imageUrl.    * @param imageUrl String representing the image URL to refresh    * @return boolean indicating whether the refresh succeeded    */   public boolean fbml_refreshImgSrc(String imageUrl) throws FacebookException, IOException {     return fbml_refreshImgSrc(new URL(imageUrl));   }    /**    * Recaches the image with the specified imageUrl.    * @param imageUrl the image URL to refresh    * @return boolean indicating whether the refresh succeeded    */   public boolean fbml_refreshImgSrc(URL imageUrl) throws FacebookException, IOException {     return extractBoolean(this.callMethod(FacebookMethod.FBML_REFRESH_IMG_SRC,                           new Pair<String, CharSequence>("url", imageUrl.toString())));   }    /**    * Publish the notification of an action taken by a user to newsfeed.    * @param title the title of the feed story    * @param body the body of the feed story    * @param images (optional) up to four pairs of image URLs and (possibly null) link URLs    * @param priority    * @return    */   public Document feed_publishActionOfUser(CharSequence title, CharSequence body,                                            Collection<Pair> images,                                            Integer priority) throws FacebookException,                                                                     IOException {     return feedHandler(FacebookMethod.FEED_PUBLISH_ACTION_OF_USER, title, body, images, priority);   }    /**    * @see FacebookRestClient#feed_publishActionOfUser(CharSequence,CharSequence,Collection,Integer)    */   public Document feed_publishActionOfUser(CharSequence title,                                            CharSequence body) throws FacebookException,                                                                      IOException {     return feed_publishActionOfUser(title, body, null, null);   }    /**    * @see FacebookRestClient#feed_publishActionOfUser(CharSequence,CharSequence,Collection,Integer)    */   public Document feed_publishActionOfUser(CharSequence title, CharSequence body,                                            Integer priority) throws FacebookException,                                                                     IOException {     return feed_publishActionOfUser(title, body, null, priority);   }    /**    * @see FacebookRestClient#feed_publishActionOfUser(CharSequence,CharSequence,Collection,Integer)    */   public Document feed_publishActionOfUser(CharSequence title, CharSequence body,                                            Collection<Pair> images) throws FacebookException,                                                                                      IOException {     return feed_publishActionOfUser(title, body, images, null);   }     /**    * Publish a story to the logged-in user's newsfeed.    * @param title the title of the feed story    * @param body the body of the feed story    * @param images (optional) up to four pairs of image URLs and (possibly null) link URLs    * @param priority    * @return    */   public Document feed_publishStoryToUser(CharSequence title, CharSequence body,                                           Collection<Pair> images,                                           Integer priority) throws FacebookException, IOException {     return feedHandler(FacebookMethod.FEED_PUBLISH_STORY_TO_USER, title, body, images, priority);   }      /**    * @see FacebookRestClient#feed_publishStoryToUser(CharSequence,CharSequence,Collection,Integer)    */   public Document feed_publishStoryToUser(CharSequence title,                                           CharSequence body) throws FacebookException,                                                                     IOException {     return feed_publishStoryToUser(title, body, null, null);   }    /**    * @see FacebookRestClient#feed_publishStoryToUser(CharSequence,CharSequence,Collection,Integer)    */   public Document feed_publishStoryToUser(CharSequence title, CharSequence body,                                           Integer priority) throws FacebookException, IOException {     return feed_publishStoryToUser(title, body, null, priority);   }    /**    * @see FacebookRestClient#feed_publishStoryToUser(CharSequence,CharSequence,Collection,Integer)    */   public Document feed_publishStoryToUser(CharSequence title, CharSequence body,                                           Collection<Pair> images) throws FacebookException,                                                                                     IOException {     return feed_publishStoryToUser(title, body, images, null);   }    protected Document feedHandler(FacebookMethod feedMethod, CharSequence title, CharSequence body,                                  Collection<Pair> images,                                  Integer priority) throws FacebookException, IOException {     assert (images == null || images.size() <= 4);      ArrayList<Pair<String, CharSequence>> params =       new ArrayList<Pair<String, CharSequence>>(feedMethod.numParams());      params.add(new Pair<String, CharSequence>("title", title));     if (null != body)       params.add(new Pair<String, CharSequence>("body", body));     if (null != priority)       params.add(new Pair<String, CharSequence>("priority", priority.toString()));     if (null != images && !images.isEmpty()) {       int image_count = 0;       for (Pair image: images) {         ++image_count;         assert (image.first != null);         params.add(new Pair<String, CharSequence>(String.format("image_%d", image_count),                                                   image.first.toString()));         if (image.second != null)           params.add(new Pair<String, CharSequence>(String.format("image_%d_link", image_count),                                                     image.second.toString()));       }     }     return this.callMethod(feedMethod, params);   }    /**    * Returns all visible events according to the filters specified. This may be used to find all events of a user, or to query specific eids.    * @param eventIds filter by these event ID's (optional)    * @param userId filter by this user only (optional)    * @param startTime UTC lower bound (optional)    * @param endTime UTC upper bound (optional)    * @return Document of events    */   public Document events_get(Integer userId, Collection<Long> eventIds, Long startTime,                              Long endTime) throws FacebookException, IOException {     ArrayList<Pair<String, CharSequence>> params =       new ArrayList<Pair<String, CharSequence>>(FacebookMethod.EVENTS_GET.numParams());      boolean hasUserId = null != userId && 0 != userId;     boolean hasEventIds = null != eventIds && !eventIds.isEmpty();     boolean hasStart = null != startTime && 0 != startTime;     boolean hasEnd = null != endTime && 0 != endTime;      if (hasUserId)       params.add(new Pair<String, CharSequence>("uid", Integer.toString(userId)));     if (hasEventIds)       params.add(new Pair<String, CharSequence>("eids", delimit(eventIds)));     if (hasStart)       params.add(new Pair<String, CharSequence>("start_time", startTime.toString()));     if (hasEnd)       params.add(new Pair<String, CharSequence>("end_time", endTime.toString()));     return this.callMethod(FacebookMethod.EVENTS_GET, params);   }    /**    * Retrieves the membership list of an event    * @param eventId event id    * @return Document consisting of four membership lists corresponding to RSVP status, with keys    *  'attending', 'unsure', 'declined', and 'not_replied'    */   public Document events_getMembers(Number eventId) throws FacebookException, IOException {     assert (null != eventId);     return this.callMethod(FacebookMethod.EVENTS_GET_MEMBERS,                            new Pair<String, CharSequence>("eid", eventId.toString()));   }     /**    * Retrieves the friends of the currently logged in user.    * @return array of friends    */   public Document friends_areFriends(int userId1, int userId2) throws FacebookException,                                                                       IOException {     return this.callMethod(FacebookMethod.FRIENDS_ARE_FRIENDS,                            new Pair<String, CharSequence>("uids1", Integer.toString(userId1)),                            new Pair<String, CharSequence>("uids2", Integer.toString(userId2)));   }    public Document friends_areFriends(Collection<Integer> userIds1,                                      Collection<Integer> userIds2) throws FacebookException,                                                                           IOException {     assert (userIds1 != null && userIds2 != null);     assert (!userIds1.isEmpty() && !userIds2.isEmpty());     assert (userIds1.size() == userIds2.size());      return this.callMethod(FacebookMethod.FRIENDS_ARE_FRIENDS,                            new Pair<String, CharSequence>("uids1", delimit(userIds1)),                            new Pair<String, CharSequence>("uids2", delimit(userIds2)));   }    /**    * Retrieves the friends of the currently logged in user.    * @return array of friends    */   public Document friends_get() throws FacebookException, IOException {     return this.callMethod(FacebookMethod.FRIENDS_GET);   }    /**    * Retrieves the friends of the currently logged in user, who are also users    * of the calling application.    * @return array of friends    */   public Document friends_getAppUsers() throws FacebookException, IOException {     return this.callMethod(FacebookMethod.FRIENDS_GET_APP_USERS);   }    /**    * Retrieves the requested info fields for the requested set of users.    * @param userIds a collection of user IDs for which to fetch info    * @param fields a set of ProfileFields    * @return a Document consisting of a list of users, with each user element    *   containing the requested fields.    */   public Document users_getInfo(Collection<Integer> userIds,                                 EnumSet<ProfileField> fields) throws FacebookException,                                                                      IOException {     // assertions test for invalid params     assert (userIds != null);     assert (fields != null);     assert (!fields.isEmpty());      return this.callMethod(FacebookMethod.USERS_GET_INFO,                            new Pair<String, CharSequence>("uids", delimit(userIds)),                            new Pair<String, CharSequence>("fields", delimit(fields)));   }    /**    * Retrieves the requested info fields for the requested set of users.    * @param userIds a collection of user IDs for which to fetch info    * @param fields a set of strings describing the info fields desired, such as "last_name", "sex"    * @return a Document consisting of a list of users, with each user element    *   containing the requested fields.    */   public Document users_getInfo(Collection<Integer> userIds,                                 Set<CharSequence> fields) throws FacebookException, IOException {     // assertions test for invalid params     assert (userIds != null);     assert (fields != null);     assert (!fields.isEmpty());      return this.callMethod(FacebookMethod.USERS_GET_INFO,                            new Pair<String, CharSequence>("uids", delimit(userIds)),                            new Pair<String, CharSequence>("fields", delimit(fields)));   }    /**    * Retrieves the user ID of the user logged in to this API session    * @return the Facebook user ID of the logged-in user    */   public int users_getLoggedInUser() throws FacebookException, IOException {     Document d = this.callMethod(FacebookMethod.USERS_GET_LOGGED_IN_USER);     return Integer.parseInt(d.getFirstChild().getTextContent());   }    /**    * Retrieves an indicator of whether the logged-in user has installed the    * application associated with the _apiKey.    * @return boolean indicating whether the user has installed the app    */   public boolean users_isAppAdded() throws FacebookException, IOException {     return extractBoolean(this.callMethod(FacebookMethod.USERS_IS_APP_ADDED));   }    /**    * Used to retrieve photo objects using the search parameters (one or more of the    * parameters must be provided).    *    * @param subjId retrieve from photos associated with this user (optional).    * @param albumId retrieve from photos from this album (optional)    * @param photoIds retrieve from this list of photos (optional)    *    * @return an Document of photo objects.    */   public Document photos_get(Integer subjId, Long albumId,                              Collection<Long> photoIds) throws FacebookException, IOException {     ArrayList<Pair<String, CharSequence>> params =       new ArrayList<Pair<String, CharSequence>>(FacebookMethod.PHOTOS_GET.numParams());      boolean hasUserId = null != subjId && 0 != subjId;     boolean hasAlbumId = null != albumId && 0 != albumId;     boolean hasPhotoIds = null != photoIds && !photoIds.isEmpty();     assert (hasUserId || hasAlbumId || hasPhotoIds);      if (hasUserId)       params.add(new Pair<String, CharSequence>("subj_id", Integer.toString(subjId)));     if (hasAlbumId)       params.add(new Pair<String, CharSequence>("aid", Long.toString(albumId)));     if (hasPhotoIds)       params.add(new Pair<String, CharSequence>("pids", delimit(photoIds)));      return this.callMethod(FacebookMethod.PHOTOS_GET, params);   }    public Document photos_get(Long albumId, Collection<Long> photoIds) throws FacebookException,                                                                              IOException {     return photos_get(null/*subjId*/, albumId, photoIds);   }    public Document photos_get(Integer subjId, Collection<Long> photoIds) throws FacebookException,                                                                                IOException {     return photos_get(subjId, null/*albumId*/, photoIds);   }    public Document photos_get(Integer subjId, Long albumId) throws FacebookException, IOException {     return photos_get(subjId, albumId, null/*photoIds*/);   }    public Document photos_get(Collection<Long> photoIds) throws FacebookException, IOException {     return photos_get(null/*subjId*/, null/*albumId*/, photoIds);   }    public Document photos_get(Long albumId) throws FacebookException, IOException {     return photos_get(null/*subjId*/, albumId, null/*photoIds*/);   }    public Document photos_get(Integer subjId) throws FacebookException, IOException {     return photos_get(subjId, null/*albumId*/, null/*photoIds*/);   }    /**    * Retrieves album metadata. Pass a user id and/or a list of album ids to specify the albums    * to be retrieved (at least one must be provided)    *    * @param userId retrieve metadata for albums created the id of the user whose album you wish  (optional).    * @param albumIds the ids of albums whose metadata is to be retrieved    * @return album objects.    */   public Document photos_getAlbums(Integer userId,                                    Collection<Long> albumIds) throws FacebookException,                                                                      IOException {     boolean hasUserId = null != userId && userId != 0;     boolean hasAlbumIds = null != albumIds && !albumIds.isEmpty();     assert (hasUserId || hasAlbumIds); // one of the two must be provided      if (hasUserId)       return (hasAlbumIds) ?              this.callMethod(FacebookMethod.PHOTOS_GET_ALBUMS, new Pair<String, CharSequence>("uid",                                                                                               Integer.toString(userId)),                              new Pair<String, CharSequence>("aids", delimit(albumIds))) :              this.callMethod(FacebookMethod.PHOTOS_GET_ALBUMS,                              new Pair<String, CharSequence>("uid", Integer.toString(userId)));     else       return this.callMethod(FacebookMethod.PHOTOS_GET_ALBUMS,                              new Pair<String, CharSequence>("aids", delimit(albumIds)));   }    public Document photos_getAlbums(Integer userId) throws FacebookException, IOException {     return photos_getAlbums(userId, null /*albumIds*/);   }    public Document photos_getAlbums(Collection<Long> albumIds) throws FacebookException,                                                                      IOException {     return photos_getAlbums(null /*userId*/, albumIds);   }    /**    * Retrieves the tags for the given set of photos.    * @param photoIds The list of photos from which to extract photo tags.    * @return the created album    */   public Document photos_getTags(Collection<Long> photoIds) throws FacebookException, IOException {     return this.callMethod(FacebookMethod.PHOTOS_GET_TAGS,                            new Pair<String, CharSequence>("pids", delimit(photoIds)));   }    /**    * Creates an album.    * @param albumName The list of photos from which to extract photo tags.    * @return the created album    */   public Document photos_createAlbum(String albumName) throws FacebookException, IOException {     return this.photos_createAlbum(albumName, null/*description*/, null/*location*/);   }    /**    * Creates an album.    * @param name The album name.    * @param location The album location (optional).    * @param description The album description (optional).    * @return an array of photo objects.    */   public Document photos_createAlbum(String name, String description,                                      String location) throws FacebookException, IOException {     assert (null != name && !"".equals(name));     ArrayList<Pair<String, CharSequence>> params =       new ArrayList<Pair<String, CharSequence>>(FacebookMethod.PHOTOS_CREATE_ALBUM.numParams());     params.add(new Pair<String, CharSequence>("name", name));     if (null != description)       params.add(new Pair<String, CharSequence>("description", description));     if (null != location)       params.add(new Pair<String, CharSequence>("location", location));     return this.callMethod(FacebookMethod.PHOTOS_CREATE_ALBUM, params);   }    /**    * Adds several tags to a photo.    * @param photoId The photo id of the photo to be tagged.    * @param tags A list of PhotoTags.    * @return a list of booleans indicating whether the tag was successfully added.    */   public Document photos_addTags(Long photoId, Collection<PhotoTag> tags)     throws FacebookException, IOException, JSONException {     assert (photoId > 0);     assert (null != tags && !tags.isEmpty());     JSONWriter tagsJSON = new JSONStringer().array();     for (PhotoTag tag: tags)       tagsJSON = tag.jsonify(tagsJSON);     String tagStr = tagsJSON.endArray().toString();      return this.callMethod(FacebookMethod.PHOTOS_ADD_TAG,                            new Pair<String, CharSequence>("pid", photoId.toString()),                            new Pair<String, CharSequence>("tags", tagStr));   }    /**    * Adds a tag to a photo.    * @param photoId The photo id of the photo to be tagged.    * @param xPct The horizontal position of the tag, as a percentage from 0 to 100, from the left of the photo.    * @param yPct The vertical position of the tag, as a percentage from 0 to 100, from the top of the photo.    * @param taggedUserId The list of photos from which to extract photo tags.    * @return whether the tag was successfully added.    */   public boolean photos_addTag(Long photoId, Integer taggedUserId, Double xPct,                                Double yPct) throws FacebookException, IOException {     return photos_addTag(photoId, xPct, yPct, taggedUserId, null);   }    /**    * Adds a tag to a photo.    * @param photoId The photo id of the photo to be tagged.    * @param xPct The horizontal position of the tag, as a percentage from 0 to 100, from the left of the photo.    * @param yPct The list of photos from which to extract photo tags.    * @param tagText The text of the tag.    * @return whether the tag was successfully added.    */   public boolean photos_addTag(Long photoId, CharSequence tagText, Double xPct,                                Double yPct) throws FacebookException, IOException {     return photos_addTag(photoId, xPct, yPct, null, tagText);   }    private boolean photos_addTag(Long photoId, Double xPct, Double yPct, Integer taggedUserId,                                 CharSequence tagText) throws FacebookException, IOException {     assert (null != photoId && !photoId.equals(0));     assert (null != taggedUserId || null != tagText);     assert (null != xPct && xPct >= 0 && xPct <= 100);     assert (null != yPct && yPct >= 0 && yPct <= 100);     Document d =       this.callMethod(FacebookMethod.PHOTOS_ADD_TAG, new Pair<String, CharSequence>("pid",                                                                                     photoId.toString()),                       new Pair<String, CharSequence>("tag_uid", taggedUserId.toString()),                       new Pair<String, CharSequence>("x", xPct.toString()),                       new Pair<String, CharSequence>("y", yPct.toString()));     return extractBoolean(d);   }    public Document photos_upload(File photo) throws FacebookException, IOException {     return /* caption */ /* albumId */photos_upload(photo, null, null);   }    public Document photos_upload(File photo, String caption) throws FacebookException, IOException {     return /* albumId */photos_upload(photo, caption, null);   }    public Document photos_upload(File photo, Long albumId) throws FacebookException, IOException {     return /* caption */photos_upload(photo, null, albumId);   }    public Document photos_upload(File photo, String caption, Long albumId) throws FacebookException,                                                                                  IOException {     ArrayList<Pair<String, CharSequence>> params =       new ArrayList<Pair<String, CharSequence>>(FacebookMethod.PHOTOS_UPLOAD.numParams());     assert (photo.exists() && photo.canRead());     this._uploadFile = photo;     if (null != albumId)       params.add(new Pair<String, CharSequence>("aid", Long.toString(albumId)));     if (null != caption)       params.add(new Pair<String, CharSequence>("caption", caption));     return callMethod(FacebookMethod.PHOTOS_UPLOAD, params);   }    /**    * Retrieves the groups associated with a user    * @param userId Optional: User associated with groups.    *  A null parameter will default to the session user.    * @param groupIds Optional: group ids to query.    *   A null parameter will get all groups for the user.    * @return array of groups    */   public Document groups_get(Integer userId, Collection<Long> groupIds) throws FacebookException,                                                                                IOException {     boolean hasGroups = (null != groupIds && !groupIds.isEmpty());     if (null != userId)       return hasGroups ?              this.callMethod(FacebookMethod.GROUPS_GET, new Pair<String, CharSequence>("uid",                                                                                        userId.toString()),                              new Pair<String, CharSequence>("gids", delimit(groupIds))) :              this.callMethod(FacebookMethod.GROUPS_GET,                              new Pair<String, CharSequence>("uid", userId.toString()));     else       return hasGroups ?              this.callMethod(FacebookMethod.GROUPS_GET, new Pair<String, CharSequence>("gids",                                                                                        delimit(groupIds))) :              this.callMethod(FacebookMethod.GROUPS_GET);   }    /**    * Retrieves the membership list of a group    * @param groupId the group id    * @return a Document containing four membership lists of    *  'members', 'admins', 'officers', and 'not_replied'    */   public Document groups_getMembers(Number groupId) throws FacebookException, IOException {     assert (null != groupId);     return this.callMethod(FacebookMethod.GROUPS_GET_MEMBERS,                            new Pair<String, CharSequence>("gid", groupId.toString()));   }    /**    * Retrieves the results of a Facebook Query Language query    * @param query : the FQL query statement    * @return varies depending on the FQL query    */   public Document fql_query(CharSequence query) throws FacebookException, IOException {     assert (null != query);     return this.callMethod(FacebookMethod.FQL_QUERY,                            new Pair<String, CharSequence>("query", query));   }    /**    * Retrieves the outstanding notifications for the session user.    * @return a Document containing    *  notification count pairs for 'messages', 'pokes' and 'shares',    *  a uid list of 'friend_requests', a gid list of 'group_invites',    *  and an eid list of 'event_invites'    */   public Document notifications_get() throws FacebookException, IOException {     return this.callMethod(FacebookMethod.NOTIFICATIONS_GET);   }    /**    * Send a request or invitations to the specified users.    * @param recipientIds the user ids to which the request is to be sent    * @param type the type of request/invitation - e.g. the word "event" in "1 event invitation."    * @param content Content of the request/invitation. This should be FBML containing only links and the     *   special tag  to specify the buttons to be included in the request.    * @param image URL of an image to show beside the request. It will be resized to be 100 pixels wide.    * @param isInvite whether this is a "request" or an "invite"     * @return a URL, possibly null, to which the user should be redirected to finalize    *    the sending of the message    */   public URL notifications_sendRequest(Collection<Integer> recipientIds, CharSequence type,   CharSequence content, URL image, boolean isInvite) throws FacebookException, IOException {     assert (null != recipientIds && !recipientIds.isEmpty());     assert (null != type);     assert (null != content);     assert (null != image);      Document d =       this.callMethod(FacebookMethod.NOTIFICATIONS_SEND_REQUEST,                        new Pair<String, CharSequence>("to_ids", delimit(recipientIds)),                       new Pair<String, CharSequence>("type", type),                       new Pair<String, CharSequence>("content", content),                       new Pair<String, CharSequence>("image", image.toString()),                       new Pair<String, CharSequence>("invite", isInvite ? "1" : "0"));     String url = d.getFirstChild().getTextContent();     return (null == url || "".equals(url)) ? null : new URL(url);   }    /**    * Send a notification message to the specified users.    * @param recipientIds the user ids to which the message is to be sent    * @param notification the FBML to display on the notifications page    * @param email the FBML to send to the specified users via email, or null    *    if no email should be sent    * @return a URL, possibly null, to which the user should be redirected to finalize    *    the sending of the email    */   public URL notifications_send(Collection<Integer> recipientIds,                                  CharSequence notification,                                 CharSequence email) throws FacebookException, IOException {     assert (null != recipientIds && !recipientIds.isEmpty());     assert (null != notification);     ArrayList<Pair<String, CharSequence>> args = new ArrayList<Pair<String,CharSequence>>(3);     args.add(new Pair<String, CharSequence>("to_ids", delimit(recipientIds)));     args.add(new Pair<String, CharSequence>("notification", email));     if (null != email)       args.add(new Pair<String, CharSequence>("email", email));     Document d = this.callMethod(FacebookMethod.NOTIFICATIONS_SEND, args);     String url = d.getFirstChild().getTextContent();     return (null == url || "".equals(url)) ? null : new URL(url);   }    protected static boolean extractBoolean(Document doc) {     String content = doc.getFirstChild().getTextContent();     return 1 == Integer.parseInt(content);       }    protected static final String CRLF = "\r\n";   protected static final String PREF = "--";   protected static final int UPLOAD_BUFFER_SIZE = 512;    public InputStream postFileRequest(String methodName,                                      Map<String, CharSequence> params) throws IOException {     assert (null != _uploadFile);     try {       BufferedInputStream bufin = new BufferedInputStream(new FileInputStream(_uploadFile));        String boundary = Long.toString(System.currentTimeMillis(), 16);       URLConnection con = SERVER_URL.openConnection();       con.setDoInput(true);       con.setDoOutput(true);       con.setUseCaches(false);       con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);       con.setRequestProperty("MIME-version", "1.0");        DataOutputStream out = new DataOutputStream(con.getOutputStream());        for (Map.Entry<String, CharSequence> entry: params.entrySet()) {         out.writeBytes(PREF + boundary + CRLF);         out.writeBytes("Content-disposition: form-data; name=\"" + entry.getKey() + "\"");         out.writeBytes(CRLF + CRLF);         out.writeBytes(entry.getValue().toString());         out.writeBytes(CRLF);       }        out.writeBytes(PREF + boundary + CRLF);       out.writeBytes("Content-disposition: form-data; filename=\"" + _uploadFile.getName() + "\"" +                      CRLF);       out.writeBytes("Content-Type: image/jpeg" + CRLF);       // out.writeBytes("Content-Transfer-Encoding: binary" + CRLF); // not necessary        // Write the file       out.writeBytes(CRLF);       byte b[] = new byte[UPLOAD_BUFFER_SIZE];       int byteCounter = 0;       int i;       while (-1 != (i = bufin.read(b))) {         byteCounter += i;         out.write(b, 0, i);       }       out.writeBytes(CRLF + PREF + boundary + PREF + CRLF);        out.flush();       out.close();        InputStream is = con.getInputStream();       return is;     }     catch (Exception e) {       System.out.println("exception: " + e.getMessage());       e.printStackTrace();       return null;     }   }    /**    * Call this function and store the result, using it to generate the    * appropriate login url and then to retrieve the session information.    * @return String the auth_token string    */   public String auth_createToken() throws FacebookException, IOException {     Document d = this.callMethod(FacebookMethod.AUTH_CREATE_TOKEN);     return d.getFirstChild().getTextContent();   }    /**    * Call this function to retrieve the session information after your user has    * logged in.    * @param authToken the token returned by auth_createToken or passed back to your callback_url.    */   public String auth_getSession(String authToken) throws FacebookException, IOException { 	if (null != this._sessionKey) {       return this._sessionKey; 	}     Document d =       this.callMethod(FacebookMethod.AUTH_GET_SESSION, new Pair<String, CharSequence>("auth_token",                                                                                       authToken.toString()));     this._sessionKey =         d.getElementsByTagName("session_key").item(0).getFirstChild().getTextContent();     this._userId =         Integer.parseInt(d.getElementsByTagName("uid").item(0).getFirstChild().getTextContent());     if (this._isDesktop)       this._sessionSecret =           d.getElementsByTagName("secret").item(0).getFirstChild().getTextContent();     return this._sessionKey;   }      /**    * Call this function to get the user ID.    *     * @return The ID of the current session's user, or -1 if none.    */   public int auth_getUserId(String authToken) throws FacebookException, IOException { 	/* 	 * Get the session information if we don't have it; this will populate 	 * the user ID as well. 	 */ 	if (null == this._sessionKey) 	  auth_getSession(authToken); 	return this._userId;   } } 

2010년 12월 16일 목요일

blogger 로의 이전

 textcube 가 Google Blogger 와 통합이 되었다는 뉴스는 보신분들이 많을것이다.
이제 실제 이전 작업을 하라고 공지가 났으니, 이제 나도 이사를 가는중이다.
새로운 주소는 http://azuroth37.blogspot.com 이다.

아 이제 새로운 인생이 시작되는 것인가...

Blogger 로의 이전

그동안 정든 내 집이었던 textcube 를 뒤로하고
이제 Blogger.com 으로 이전을 하라고 공지가 나서
지금 이사하는 중이다.

이제 새로운 인생의 시작인 것인가...

2010년 10월 6일 수요일

2010년 10월, 구덕의 Google Korea 탐방기.

지난 번 포스팅 (http://azuroth.textcube.com/76) 했던대로
구글 코리아로부터 개인적으로 초대장을 받아서 오늘 구글 코리아에서 있은
<Google Blogger's Night> 행사에 다녀왔습니다.

일단 구글로부터 개인적으로 초대장을 받았다는 것만으로도
덕이 넘치는 구덕으로서의 업적이어서 즐거운 마음으로 도착하였습니다.
도착하니 바로 공짜, 부페 음식이 차려져 있었습니다. 맛있었어용.
차려진 음식의 모습은...

식사를 마치고 본 행사에 들어갔습니다.
수많은 구덕들이 참여한 가운데, 오늘 행사의 주제는
이번에 새로이 런칭한 구글 보이스 서비스에 대한 홍보였습니다.

지난 번 구글 음성 검색을 런칭하는 행사에도 참여했습니다만
그 당시 검색어로 거의 애국가 한 절을 불러도 다 입력이 되는 뛰어난 성능을 보이는 것을 보고 어떤 분이 이 음성으로 그럼 키보드 대신 입력수단으로 사용하면 어떻겠느냐는 의견을 주셨습니다. 당시 설명하시던 Pruduct Manager 께서도 좋은 방법이지만 아직 완성도에 있어서
좀 더 해야할 일이 있음을 말씀하셨었는데, 이제 이번에 음성 서비스 런칭은
그러한 음성 관련의 더욱 진일보한 기술 혁신으로 이제 입력 수단으로 음성을 사용할 수 있게 된 것이었습니다.

일단 시연도 보여주셨는데 과연 구글이 한 만큼 성능은 매우 뛰어나서
음성입력 수단으로, GTalk 를 한다던지, Gmail 을 보낸다던지 하는데 쓸 수 있을 만큼
훌륭하였습니다. 크어어어어어어어어어어... 구글 만세!!!

사회를 보신 선정님의 모습. 미모를 자랑하십니다.

행사를 시작하고 저의 경우는
서울에 왔으니, 미녀가 있는지 검색 탐색하는 것은
오덕이 행해야할 마땅한 도리이며 습성이기 때문에 우선 구글의 직원 분들도 열심히 살펴보았습니다. 일단 시회보신 선정님도 미인, Google Voice 서비스를 맡으신
이해민 Pruduct Manager 님도 미인, 그리고 테이블 당 앉아주신 각 도우미 직원불들도 상큼하신 젋은 미인이셨고, 한 분은 키도 크고 얼굴이 작아서 모델, 연예인 포스를 풍겨주시기도 했습니다.
한마디로 축약해서 말씀드리자면 삼성전자보다 훨씬 물이 좋았다는거죠.

어쨋든 아래 사진은 또 이해민 PM 님이 시연, 설명 해주시는 모습.

설명하신 후에 개발을 맡아주신 Technical Leader 분과 함께 시연도 해주셨습니다.
GTalk 에 음성입력을 통해 대화를 나누시는 모습입니다.

성능은 만족할 만 했습니다. 만세!!!

모든 시연이 끌난 후에는 각 참가자별 5명 정도씩 조를 이루어 각자의 테이블에 나누어 앉은 후에 한 명씩 구글 직원분들이 도우미로 오신 후 우리는 각자의 넥서스 원 전화기 한 대씩을 통하여 음성 검색 관련 게임을 하였습니다.
테이블 별 경쟁이었죠. 일단 우리 테이블에 오셔서 도와주신 상큼하신
박지현 님을 소개드립니다. 물론 본인에게 허락을 받은 후에 여기 사진을 올립니다.

우리 테이블에 앉은분이 갤럭시 S도 가져오셔서
넥서스 원과 갤럭시 S가 함께 나란히 있는 모습을 찍어봅니다.

이제 게임을 시작하였는데, 또 여기서 중대한 일이 발생했습니다.
저희 테이블이 한 종목에서 1등을 수립하고야 만 것입니다.
아 아름답습니다. 그리하여 우리는 한가지 상품을 더 받게 되었으니, 그것은 구글 마크가 선명하게 찍혀있는 여행용 가방 세트입니다. 매우 구덕스런 상품입니다. 지난 번 주신 가방은 구글 마크는 없고 음성검색 마크만 있었는데, 이번 것은 구글 마크가 선명히 보임으로써 "나는 구덕이오..." 라는 메시지를 풍기고 있습니다.

여러가지 게임을 즐기며 우리 구덕들은 즐거운 시간을 보냈습니다.
구글에서 노니 덕이 마구 쌓이더군요.
구글에서 덕을 펼치며 옹기종기 놀고 있는 구덕들의 모습입니다.

여러가지 시연, 게임 등 행사를 마치고 나서 우리는 집에 갈 시간이 되었습니다.
구덕에게도 돌아갈 집은 있는 것이었습니다.

마지막으로 또 전원에게 기념품을 나누어 주시더군요.
무엇일까 궁금했는데
집에 돌아와서 보니 어여쁜 티셔츠였습니다.
이 또한 구덕을 자랑할 수 있는 아이템이군요.
이 티셔츠를 입고 구덕을 자랑하는 이 나라의 일꾼이 되겠습니다.

이로써 오늘의 구글 탐방기를 마칩니다.
이번에 구덕으로써 선발이 되었으니 다음부터 행사가 있을 때는 또 불러주시겠죠.
아 구덕이라서 햄볶아요...

2010년 10월 2일 토요일

또 구덕모임에 정식초대받은 나는 완전 구덕?

구글 코리아로 부터 초대장이 왔습니다.
다음주 수요일에 Blogger 대상 행사가 있다고 오라고 하시는군요.
나는 진정 구글의 선택을 받은
진정한 구덕인가요...

2010년 7월 13일 화요일

또 하나의 구덕 아이템을 득템하다...

지난 번에 구글 넥서스 원 유저 행사에 가서는
음성 검색 마크가 박힌 가방과 물통을 얻어왔습니다.
이번에는 구글에서 진행하는 설문조사에 참여하여 오늘 안드로이드 마크가 박힌 우산을 받게 되었습니다. 참 작고 귀엽고 쓸만한 우산이네요. 구덕이라서 행복합니다.


아주 여태까지 태어나서 본 것중에 가장 작은 사이즈입니다.

그리고 이것은 우산을 펼쳤을때의 앙증맞은 안드로보이의 그림과 함께...



아... 구덕이라 행복해요...