package de.tud.kom.socom.components.social; import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.json.JSONException; import org.json.JSONObject; import de.tud.kom.socom.GlobalConfig; import de.tud.kom.socom.SocomComponent; import de.tud.kom.socom.database.game.GameDatabase; import de.tud.kom.socom.database.game.HSQLGameDatabase; import de.tud.kom.socom.database.influence.HSQLInfluenceDatabase; import de.tud.kom.socom.database.user.HSQLUserDatabase; import de.tud.kom.socom.util.EasyEncrypter; import de.tud.kom.socom.util.JSONUtils; import de.tud.kom.socom.util.Logger; import de.tud.kom.socom.util.LoggerFactory; import de.tud.kom.socom.util.ResourceLoader; import de.tud.kom.socom.util.SocomRequest; import de.tud.kom.socom.util.attributemapping.AttributeMap; import de.tud.kom.socom.util.attributemapping.UniqueAttribute; import de.tud.kom.socom.util.datatypes.NetworkPost; import de.tud.kom.socom.util.datatypes.NetworkPostSupport; import de.tud.kom.socom.util.datatypes.Profile; import de.tud.kom.socom.util.exceptions.ContentDeletedException; import de.tud.kom.socom.util.exceptions.CookieNotFoundException; import de.tud.kom.socom.util.exceptions.IllegalParameterException; import de.tud.kom.socom.util.exceptions.MissingTokenException; import de.tud.kom.socom.util.exceptions.NoSNConnectionException; import de.tud.kom.socom.util.exceptions.NotImplementedException; import de.tud.kom.socom.util.exceptions.SocialNetworkUnsupportedException; import de.tud.kom.socom.util.exceptions.SocomException; /** * * @author rhaban * */ public class SocialNetworkManager extends SocomComponent implements GlobalConfig { private static final Logger logger = LoggerFactory.getLogger(); private static final String URL_PATTERN = "social"; private static SocialNetworkManager instance = new SocialNetworkManager(); private SocialNetworkManager() { } public static SocialNetworkManager getInstance() { return instance; } /** * Receive an URL to log into a social network. Needs the users id from * session. * * @param network * (string of social network) * @return The URL * @throws JSONException */ public int loginURL(SocomRequest req) throws JSONException, SocomException { long uid = req.getUid(); long gameinstid = req.getCurrentGameInst(); String network = req.getParam("network"); SNConnection nwconn = getConnection(network, gameinstid); if (nwconn == null) return 4; JSONObject json = new JSONObject().put("url", nwconn.getLoginURL(uid, gameinstid)); req.addOutput(JSONUtils.JSONToString(json)); return 0; } /** * Method used from Facebook to send users access token. Do not use. * @throws IOException */ public int requestToken(SocomRequest req) throws JSONException, SQLException, SocomException, IOException { String code = req.getParam("code"); String identifier = req.getParam("state"); String[] parts = identifier.split(";"); if (parts.length != 3) return 4; String network = parts[0]; long gameinstid = Long.valueOf(parts[1]); String[] idParts = parts[2].split("-"); long uid = Long.valueOf(idParts[0]); byte[] b = new byte[idParts.length - 1]; for (int i = 1; i < idParts.length; i++) { b[i - 1] = (byte) Integer.parseInt(idParts[i], 16); } String password = EasyEncrypter.getInstance().decryptString(b); boolean valid = HSQLUserDatabase.getInstance().validateUser(uid, password); if (!valid) throw new ContentDeletedException(); SNConnection nw = getConnection(network, gameinstid); if (uid < 0 || nw == null) return 4; // special case implementation to directly add tokens to Database if (code.equals(GlobalConfig.SOCIALNETWORK_CODE_DIRECT_TOKEN_SAVE)) { nw.saveAccessToken(uid, req.getParam("token")); } else { nw.requestAccessToken(code, uid); } nw.updateFriendList(uid); String gameident = HSQLGameDatabase.getInstance().getGameIdentifier(gameinstid); req.addOutput("<head>\n<meta http-equiv=\"refresh\" content=\"0; URL=../../web/#/" + gameident + "/profiles/nwsucc-" + network + "\">\n</head>"); return 0; } /** * Creates a Connection-Client-Object for the current user, used for example * when the middleware was restarted. Normally you do not need to use this * method */ public int relogin(SocomRequest req) throws SQLException, JSONException, SocomException { long uid = req.getUid(); long gameinstid = req.getCurrentGameInst(); String network = req.getParam("network"); if (network == null) return 4; // unexpected parameter SNConnection nw = getConnection(network, gameinstid); if (nw == null) return 4; boolean success = nw.login(uid); req.addOutput(JSONUtils.getSuccessJsonString(success)); return 0; } /** * Logs the current user out of given network. Means: deletes the token and * Connection-Client-Object. * * @param network * @return success boolean * @throws SQLException * @throws JSONException */ public int logout(SocomRequest req) throws SQLException, JSONException, SocomException { long uid = req.getUid(); long gameinstid = req.getCurrentGameInst(); String network = req.getParam("network"); if (network == null) return 4; // unexpected parameter SNConnection nw = getConnection(network, gameinstid); if (nw == null) return 4; nw.logout(uid); req.addOutput(JSONUtils.getSuccessJsonString()); return 0; } /** * Show whether the user is logged in into the given network, so that its * possible to use network-methods. Needs the users id from session. * * @param network * @return Network-boolean connection if the user is logged in in the * specific network * @throws JSONException * @throws SQLException */ public int isLoggedIn(SocomRequest req) throws JSONException, SQLException, SocomException { long uid = req.getUid(); long gameinstid = req.getCurrentGameInst(); String network = req.getParam("network"); if (network == null) return 4; // unexpected parameter SNConnection nw = getConnection(network, gameinstid); if (nw == null) return 4; if (!nw.isLoggedIn(false, uid)) nw.login(uid); JSONObject json = new JSONObject(); json.put("loggedin", nw.isLoggedIn(false, uid)); json.put("network", network.toString()); req.addOutput(JSONUtils.JSONToString(json)); return 0; } /** * Connect a socialnetwork application to a socom-game * * @param network * @param game name of the game * @param password games password * @param app_id socialnetwork specific application id * @param app_secret socielnetwork specific application secret * @param autofill_redirects optional (default true): if true the * token_redirect_url and general_redirect_url are filled * auto automatically which may cause unusability when different * token-request methods are used * @param token_redirect_url optional (only when autofill_.. is false) default: http://<SOCOM_HOST>/servlet/social/requestToken * @param general_redirect_url optional (only when autofill_.. is false) default: http://<SOCOM_HOST>/web * @return success json * @throws IllegalParameterException * @throws SocomException * @throws SQLException */ public int connectGameApp(SocomRequest req) throws IllegalParameterException, SocomException, SQLException { String network = req.getParam("network"); boolean networkValid = false; for(String nw : SOCIALNETWORK_ALL) { if(network.equalsIgnoreCase(nw)) { networkValid = true; break; } } if(!networkValid) throw new IllegalParameterException("network"); String game = req.getParam("game"); String gamepassword = req.getParam("password"); long appId = Long.parseLong(req.getParam("app_id")); String appSecret = req.getParam("app_secret"); boolean autofillRedirects = req.getParam("autofill_redirects", true); String token_redirect = null, general_redirect = null; if(!autofillRedirects) { token_redirect = req.getParam("token_redirect_url"); general_redirect = req.getParam("general_redirect_url"); } GameDatabase gameDB = HSQLGameDatabase.getInstance(); long gameId = gameDB.authenticateGame(game, gamepassword); boolean success = gameDB.addSNApp(network, gameId, appId, appSecret, autofillRedirects, token_redirect, general_redirect); req.addOutput(JSONUtils.getSuccessJsonString(success)); return 0; } /** * Connects a page managed by the current user to the current game so that * postings to the pages feed are possible * * The current user has to be connected with the network in the specific game * * @param network * @param gamepassword * @param pageidentifier socialnetwork-specific identifier of the page * @return success boolean * @throws SocomException * @throws SQLException * @throws JSONException * @throws IOException */ public int connectGamePage(SocomRequest req) throws SocomException, SQLException, IOException, JSONException { long uid = req.getUid(); String network = req.getParam("network"); String gamepassword = req.getParam("gamepassword"); String pageidentifier = req.getParam("pageidentifier"); long gameinstid = req.getCurrentGameInst(); HSQLGameDatabase.getInstance().authenticateGameInstance(gameinstid, gamepassword); SNConnection sn = getConnection(network, gameinstid); boolean isLoggedIn = sn.login(uid); if(!isLoggedIn) { throw new SocialNetworkUnsupportedException(network + " (user not authenticated) "); } String token = sn.getGamePageToken(uid, gameinstid, pageidentifier); if(token == null) { throw new SocialNetworkUnsupportedException("pageidentifier not visible to user"); } boolean success = HSQLGameDatabase.getInstance().connectSocialNetworkPage(uid, gameinstid, network, pageidentifier, token); req.addOutput(JSONUtils.getSuccessJsonString(success)); return 0; } /** * Shows network friends of every network he is logged in with their snuids. * Needs the users id from session. * * @return List of Network-Profiles * @throws JSONException */ public int getNetworkFriends(SocomRequest req) throws JSONException, SocomException { long uid = req.getUid(); long gameinstid = req.getCurrentGameInst(); List<Profile> result = new LinkedList<Profile>(); for (String nw : SOCIALNETWORK_ALL) { SNConnection nwconn = getConnection(nw, gameinstid); if (nwconn == null) return 4; try { nwconn.login(uid); if (nwconn.isLoggedIn(false, uid)) for (Profile p : nwconn.getFriends(uid)) result.add(p); } catch (NotImplementedException e) { continue; } } req.addOutput(JSONUtils.JSONToString(new JSONObject().put("profiles", result))); return 0; } /** * Shows the networks, which are currently supported by Socom. * * @return List of Networks * @throws JSONException * @throws */ public int getSupportedNetworks(SocomRequest req) throws JSONException, SocomException { JSONObject json = new JSONObject(); json.put("networks", SOCIALNETWORK_ALL); req.addOutput(JSONUtils.JSONToString(json)); return 0; } /** * Shows user Information collected from the social networks. Needs the * users id from session. * * @return Attribute-Map * @throws JSONException * @throws IOException */ public int getProfileData(SocomRequest req) throws JSONException, SocomException, IOException { long uid = req.getUid(); long gameinstid = req.getCurrentGameInst(); AttributeMap map = new AttributeMap(); boolean atLeastOneNetwork = false; for (String nw : SOCIALNETWORK_ALL) { try { SNConnection nwconn = getConnection(nw, gameinstid); nwconn.login(uid); if (nwconn.isLoggedIn(false, uid)) { atLeastOneNetwork = true; nwconn.getAttributes(uid, "me", map); int friendCount = nwconn.getFriends(uid).size(); map.addAttribute("friendCount", new UniqueAttribute(String.valueOf(friendCount))); } } catch (NoSNConnectionException e) { continue; } catch (NotImplementedException e) { continue; } } if(atLeastOneNetwork) { req.addOutput(JSONUtils.JSONToString(new JSONObject().put("data", map))); } else { req.addOutput(JSONUtils.JSONToString(new JSONObject().put("error", "no social network connected"))); } return 0; } /** * Shows user information collected from the specific network from the given * usersnid. * * @param network * @param usersnid * @return Attribute-Map * @throws JSONException * @throws SocomException * @throws IOException */ public int getProfileDataOf(SocomRequest req) throws JSONException, SocomException, IOException { long uid = req.getUid(); long gameinstid = req.getCurrentGameInst(); String snuid = req.getParam("usersnid"); String network = req.getParam("network"); AttributeMap map = new AttributeMap(); SNConnection nwconn = getConnection(network, gameinstid); nwconn.login(uid); if (nwconn.isLoggedIn(false, uid)) { nwconn.getAttributes(uid, snuid, map); req.addOutput(JSONUtils.JSONToString(new JSONObject().put("data", map))); } else { req.addOutput(JSONUtils.JSONToString(new JSONObject().put("error", "social network not connected"))); } return 0; } /** * Publish a text-message on a) the users sn-walls if somebody is logged in * b) on games sn-walls * * @param message * @param publishonpage whether the post should be done on the games page instead * of users feed (see connectGamePage in UserManager) * @return post-ids of posts made * @throws JSONException * @throws SQLException * @throws IOException */ public int publishOnFeed(SocomRequest req) throws JSONException, SocomException, SQLException, IOException { long uid = req.getUid(); long gameinstid = req.getCurrentGameInst(); boolean publishOnPage = req.getParam("publishonpage", false); String message = req.getParam("message"); Map<String, String> result = new HashMap<String, String>(); for (String nw : SOCIALNETWORK_ALL) { try { SNConnection nwconn = getConnection(nw, gameinstid); nwconn.login(uid); if (nwconn.isLoggedIn(publishOnPage, uid)) result.put(nw, nwconn.publishOnFeed(publishOnPage, message, uid)); } catch (NotImplementedException e) { continue; } //XXX maybe catch sn-network exception and let other networks allow continuing (may be executed even before) } JSONObject json = new JSONObject(); json.put("ids", result); req.addOutput(JSONUtils.JSONToString(json)); return 0; } /** * Publish a Message (only text) on users walls including a url to the given * influence. * * @param message * @param influence * Influence-ID * @param publishonpage whether the post should be done on the games page instead * of users feed (see connectGamePage in UserManager) * @return Post-Ids * @throws JSONException * @throws SQLException * @throws IOException */ public int publishInfluenceOnfeed(SocomRequest req) throws JSONException, SocomException, SQLException, IOException { long uid = req.getUid(); long gameinstid = req.getCurrentGameInst(); String message = req.getParam("message"); String influenceId = req.getParam("influence"); boolean publishOnPage = req.getParam("publishonpage", false); // filter invisible or unfound HSQLInfluenceDatabase.getInstance().getInfluenceId(uid, influenceId); Map<String, String> result = publishInfluence(publishOnPage, uid, gameinstid, message, influenceId); JSONObject json = new JSONObject(); json.put("ids", result); req.addOutput(JSONUtils.JSONToString(json)); return 0; } /* * should not be called from outside (not even possible since * parameter-length != 1) */ public Map<String, String> publishInfluence(boolean publishOnPage, long uid, long gameinstid, String message, String influenceId) throws SocomException, SQLException, IOException, JSONException { Map<String, String> result = new HashMap<String, String>(); String gameident = HSQLGameDatabase.getInstance().getGameIdentString(gameinstid); String inflUrl = ResourceLoader.buildPublicServerUrl() + "/web/#" + gameident + "/influence/" + influenceId; for (String nw : SOCIALNETWORK_ALL) { SNConnection nwconn = getConnection(nw, gameinstid); try { if(!publishOnPage) nwconn.login(uid); if (nwconn.isLoggedIn(publishOnPage, uid)) result.put(nw, nwconn.publishOnFeedWithUrl(publishOnPage, message, uid, inflUrl)); } catch (NotImplementedException e) { continue; } } return result; } /** * POST-Method * * Publish media-based content on users walls. * * All Parameters must be given as cookies. Furthermore the HTTP-POST must * include a stream containing the file which should be uploaded * * @param message * @param type * (photos/videos) * @param extension * The file's extension * @param publishonpage whether the post should be done on the games page instead * of users feed (see connectGamePage in UserManager) * @return Post-Ids * @throws JSONException * @throws SQLException */ public int publishMediaOnFeed(SocomRequest req) throws JSONException, SocomException, SQLException { try { long uid = req.getUid(); long gameinstid = req.getCurrentGameInst(); boolean publishOnPage = false; try{ publishOnPage = Boolean.parseBoolean(req.getCookieVal("publishonpage")); } catch (CookieNotFoundException e) { /* ok, use false */ } String message = req.getCookieVal("message"); // type must be 'videos' or 'photos' String type = req.getCookieVal("type"); InputStream is = req.getInputStream(); Map<String, String> result = new HashMap<String, String>(); for (String nw : SOCIALNETWORK_ALL) { SNConnection nwconn = getConnection(nw, gameinstid); try { if(!publishOnPage) nwconn.login(uid); if (nwconn.isLoggedIn(publishOnPage, uid)) { //FIXME: this might become a problem to read the stream multiple times when supporting multiple networks result.put(nw, nwconn.publishOnFeedWithMedia(publishOnPage, type, message, is, uid)); } } catch (MissingTokenException e) { return e.getErrorCode(); } catch (NotImplementedException e) { continue; } } JSONObject json = new JSONObject(); json.put("ids", result); req.addOutput(JSONUtils.JSONToString(json)); return 0; } catch (IOException e1) { logger.Error(e1); return 2; } } /** * Deletes a post from the feed * * @param post * postId * @param network * sn * @return success boolean * @throws JSONException * @throws SocomException * @throws IOException */ public int deletePost(SocomRequest req) throws JSONException, SocomException, IOException { long uid = req.getUid(); long gameinstid = req.getCurrentGameInst(); String postId = req.getParam("post"); String network = req.getParam("network"); SNConnection nw = getConnection(network, gameinstid); boolean success = nw.deletePost(uid, postId); req.addOutput(JSONUtils.getSuccessJsonString(success)); return 0; } /** * Shows a post and its comments (without containing media) and comments * posted to the post. Needs the users id from session. * * @param post * The id of the post to read * @param network * @return Post with its comments * @throws JSONException * @throws IOException */ public int readPost(SocomRequest req) throws JSONException, SocomException, IOException { long uid = req.getUid(); long gameinstid = req.getCurrentGameInst(); String postid = req.getParam("post"); String nw = req.getParam("network"); NetworkPost post = null; List<NetworkPost> comments = null; SNConnection nwconn = getConnection(nw, gameinstid); nwconn.login(uid); if (nwconn.isLoggedIn(false, uid)) { post = nwconn.readPost(uid, postid); comments = nwconn.readComments(uid, postid); } req.addOutput(JSONUtils.JSONToString(new JSONObject().put("post", post).put("comments", comments))); return 0; } /** * Publish a comment on a post. * * @param post * The Post id to comment on * @param message * @param network * @return success boolean * @throws JSONException * @throws IOException */ public int comment(SocomRequest req) throws JSONException, SocomException, IOException { long uid = req.getUid(); long gameinstid = req.getCurrentGameInst(); String postid = req.getParam("post"); String message = req.getParam("message"); String nw = req.getParam("network"); boolean success = false; SNConnection nwconn = getConnection(nw, gameinstid); nwconn.login(uid); if (nwconn.isLoggedIn(false, uid)) success = nwconn.comment(uid, postid, message); req.addOutput(JSONUtils.getSuccessJsonString(success)); return 0; } /** * Gets the count of Supporters (e.g. likes on facebook) to a specific post * and gives information about supporters * * @param post * @param network * @return count * @throws JSONException * @throws IOException */ public int getSupporter(SocomRequest req) throws JSONException, SocomException, IOException { long uid = req.getUid(); long gameinstid = req.getCurrentGameInst(); String postid = req.getParam("post"); String nw = req.getParam("network"); if (postid == null || postid.equals("")) throw new IllegalParameterException("post must be a longer string identifier than empty."); SNConnection nwconn = getConnection(nw, gameinstid); nwconn.login(uid); if (nwconn.isLoggedIn(false, uid)) { NetworkPostSupport support = nwconn.getSupports(uid, postid); req.addOutput(JSONUtils.JSONToString(new JSONObject().put("support", support))); } else { req.addOutput(JSONUtils.getSuccessJsonString(false)); } return 0; } /** * lookup a person from a social network, show his socom-id if he has a * socom account * * @param network * @param snuid * @return uid * @throws SQLException * @throws JSONException */ public int getSocomId(SocomRequest req) throws SQLException, JSONException, SocomException { long uid = req.getUid(); if (uid < 0) return 2; // missing uid in session String network = req.getParam("network"); String snuid = req.getParam("snuid"); int id = HSQLUserDatabase.getInstance().getIDOf(network, snuid); req.addOutput(JSONUtils.JSONToString(new JSONObject().put("uid", id))); return 0; } /** * Returns a link to a thumbnail of the users photo * * @param usersnid * @param network * @return The thumbnail-URL * @throws IOException * @throws JSONException */ public int getPictureThumbnail(SocomRequest req) throws IOException, JSONException, SocomException { String snuid = req.getParam("usersnid"); String network = req.getParam("network"); String url = getConnection(network, 0).getPhotoThumbnailUrl(snuid); req.addOutput(JSONUtils.JSONToString(new JSONObject().put("thumbnail_url", url))); return 0; } /** * returns NetworkConnection for given network * * @param network * Network, whose connection you need * @param gameid * Gameinstance to identify game-dependent sn-app * @return network connection * @throws NoSNConnectionException * @throws NotImplementedException */ public SNConnection getConnection(String network, long gameid) throws NoSNConnectionException, NotImplementedException { if (network.equalsIgnoreCase(SOCIALNETWORK_FACEBOOK)) { return FacebookConnection.getConnection(gameid); } else if (network.equalsIgnoreCase(SOCIALNETWORK_GOOGLEPLUS)) return null; else return null; } /** * URL PATTERN IS "social" */ @Override public String getUrlPattern() { return URL_PATTERN; } }