package de.tud.kom.socom.components.social;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import de.tud.kom.socom.GlobalConfig;
import de.tud.kom.socom.database.game.HSQLGameDatabase;
import de.tud.kom.socom.database.user.HSQLUserDatabase;
import de.tud.kom.socom.database.user.UserDatabase;
import de.tud.kom.socom.facebook.predef.FBDeletePredefs;
import de.tud.kom.socom.facebook.predef.FBIdentities;
import de.tud.kom.socom.facebook.predef.FBMediaType;
import de.tud.kom.socom.facebook.predef.FBPublishPredefs;
import de.tud.kom.socom.facebook.predef.FBReadPredefs;
import de.tud.kom.socom.util.Logger;
import de.tud.kom.socom.util.LoggerFactory;
import de.tud.kom.socom.util.attributemapping.AttributeMap;
import de.tud.kom.socom.util.attributemapping.networkparsing.AttributeParser;
import de.tud.kom.socom.util.attributemapping.networkparsing.FacebookAttributeParser;
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.IllegalAccessException;
import de.tud.kom.socom.util.exceptions.MediaTypeNotSupportedException;
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.PostNotAvailableException;
import de.tud.kom.socom.util.exceptions.SocialNetworkException;
import de.tud.kom.socom.util.exceptions.SocialNetworkUnsupportedException;
import de.tud.kom.socom.util.exceptions.UserNotFoundException;
/**
*
* @author rhaban
*
*/
public class FacebookConnection implements SNConnection, GlobalConfig {
private String OAUTH_URL;
private String TOKEN_REQUEST_URL;
private long gameinstid;
private static Map<Long, FacebookConnection> connectionPerGame;
// managing every client. every player needs to have its own FacebookClient
// to connect
private static UserDatabase user_db;
private static Logger logger;
public FacebookConnection(long gameinstid) throws NoSNConnectionException {
if(user_db == null) user_db = HSQLUserDatabase.getInstance();
if(logger == null) logger = LoggerFactory.getLogger();
this.gameinstid = gameinstid;
try {
gatherAppInformation(gameinstid);
} catch (SQLException e) {
throw new NoSNConnectionException();
}
}
private void gatherAppInformation(long gameinstid) throws NoSNConnectionException, SQLException {
long appid = HSQLGameDatabase.getInstance().getSNAppId(SOCIALNETWORK_FACEBOOK, gameinstid);
String appsecret = HSQLGameDatabase.getInstance().getSNAppSecret(SOCIALNETWORK_FACEBOOK, gameinstid);
String token_redirect_url = HSQLGameDatabase.getInstance().getSNTokenRedirectUrl(SOCIALNETWORK_FACEBOOK, gameinstid);
this.OAUTH_URL = "https://www.facebook.com/dialog/oauth?"
+ "client_id=" + appid
+ "&redirect_uri=" + token_redirect_url
+ "&scope=user_about_me,publish_stream,read_stream,offline_access,read_friendlists,manage_friendlists,manage_pages,photo_upload&state=";
this.TOKEN_REQUEST_URL = "https://graph.facebook.com/oauth/access_token?"
+ "client_id=" + appid + "&redirect_uri=" + token_redirect_url + "&client_secret="
+ appsecret + "&code=";
}
private String getFBIdentifier(boolean page, String id) {
return this.gameinstid + (page?"_page_":"_user_") + id;
}
/**
* @param uid
* @param page true if on behalf of a page or false if directly a user
* @return true if the user has a facebook client
*/
private boolean containsClient(boolean page, String uid) {
return FBIdentities.getFBIdent(getFBIdentifier(page, uid)) != null;
}
/**
* creates a FacebookClient for the user
*
* @param uid
* @param page true if on behalf of a page or false if directly a user
* @param token
*/
private synchronized void addClient(boolean page, String uid, String token) {
FBIdentities.addFBIdent(getFBIdentifier(page, uid), token);
}
/**
* removes the FacebookClient for the user
* @param uid
* @param page true if on behalf of a page or false if directly a user
*/
private synchronized void removeClient(boolean page, String uid) {
FBIdentities.removeFBIdent(getFBIdentifier(page, uid));
}
/**
* @param uid
* @param page true if on behalf of a page or false if directly a user
* @return the facebook client
*/
private synchronized FBIdentities getClient(boolean page, String uid) {
return FBIdentities.getFBIdent(getFBIdentifier(page, uid));
}
@Override
public List<Profile> getFriends(long uid) throws NotImplementedException, JSONException {
List<Profile> result = new LinkedList<Profile>();
try {
JSONObject fbresult = FBReadPredefs.executeReadFriends(getClient(false, String.valueOf(uid)));
JSONArray friends = fbresult.getJSONArray("data");
for (int i = 0; i < friends.length(); i++) {
result.add(new Profile(friends.getJSONObject(i).getString("name"), SOCIALNETWORK_FACEBOOK,
friends.getJSONObject(i).getString("id")));
}
} catch (IOException e) {
logger.Error(e);
}
return result;
}
@Override
public String getLoginURL(long uid, long gameinstid) throws NotImplementedException, UserNotFoundException {
String secret = null;
try {
secret = HSQLUserDatabase.getInstance().getUsersSecretEncrypted(uid);
} catch (SQLException e) {
logger.Error(e);
}
String uidsecret = uid + "-" + secret;
return OAUTH_URL + SOCIALNETWORK_FACEBOOK + ";" + gameinstid + ";" + uidsecret;
}
/**
* request with the given code an access token for the user with userID and
* finally saves all information in the database
*/
public void requestAccessToken(String code, long uid) {
try {
URL url = new URL(TOKEN_REQUEST_URL + code);
BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()));
String readLine = reader.readLine();
String token = readLine.substring(13); // "access_token=".length()
token = token.split("&")[0];
saveAccessToken(uid, token);
} catch (IOException e) {
logger.Error(e);
} catch (JSONException e) {
logger.Error(e);
}
}
@Override
/**
* see: https://developers.facebook.com/docs/reference/api/page/#page_access_tokens
*/
public String getGamePageToken(long uid, long gameinstid, String pageidentifier) throws IOException, JSONException, SQLException {
String access_token = user_db.getSNToken(uid, gameinstid, "facebook");
URL url = new URL("https://graph.facebook.com/me/accounts?access_token=" + access_token);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.connect();
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuffer responseBuffer = new StringBuffer();
String line;
while((line = reader.readLine()) != null)
responseBuffer.append(line);
JSONObject response = new JSONObject(responseBuffer.toString());
JSONArray data = response.getJSONArray("data");
for(int i = 0; i < data.length(); i++){
JSONObject dataObject = data.getJSONObject(i);
if(dataObject.getString("id").equals(pageidentifier)){
String page_token = dataObject.getString("access_token");
addClient(true, String.valueOf(uid), page_token);
return page_token;
}
}
return null;
}
@Override
public void saveAccessToken(long uid, String token) throws IOException, JSONException {
try {
/* create FacebookClient for Connections */
addClient(false, String.valueOf(uid), token);
/* save token for userid */
user_db.addNetworkIdentification(uid, SOCIALNETWORK_FACEBOOK, gameinstid, getUsersNetworkID(uid), token);
} catch (SocialNetworkUnsupportedException e) {
logger.Error(e);
} catch (SQLException e) {
logger.Error(e);
}
}
@Override
public void updateFriendList(long uid) throws NotImplementedException, JSONException {
if (containsClient(false, String.valueOf(uid))) {
List<Profile> friends = getFriends(uid);
try {
user_db.addSNFriends(uid, SOCIALNETWORK_FACEBOOK, friends, true);
// friendships in facebook are two-way friendships
} catch (SQLException e) {
logger.Error(e);
}
}
}
@Override
public boolean login(long uid) throws MissingTokenException, ContentDeletedException,
IllegalAccessException {
if (isLoggedIn(false, uid))
return true;
try {
String token = user_db.getSNToken(uid, gameinstid, SOCIALNETWORK_FACEBOOK);
if (token == null || token.length() <= 1)
return false;
addClient(false, String.valueOf(uid), token);
} catch (SQLException e) {
logger.Error(e);
}
return isLoggedIn(false, uid);
}
/**
* log user out: - delete access token & delete FacebookClient for this user
*/
public void logout(long uid) throws SQLException {
try {
user_db.removeNetworkToken(uid, gameinstid, SOCIALNETWORK_FACEBOOK);
} catch (SocialNetworkUnsupportedException e) {
logger.Error(e);
}
removeClient(false, String.valueOf(uid));
}
/**
* true if the user is logged in here
*/
public boolean isLoggedIn(boolean page, long uid) {
return containsClient(page, String.valueOf(uid));
}
@Override
public String publishOnFeed(boolean publishOnPage, String message, long uid) throws SocialNetworkUnsupportedException, SQLException, JSONException, IOException, SocialNetworkException {
if(publishOnPage && !isLoggedIn(publishOnPage, uid)) {
reloginPage(uid);
}
FBIdentities ident = getClient(publishOnPage, String.valueOf(uid));
JSONObject publishMessageResponse = FBPublishPredefs.executePublishMessage(ident, message);
if(publishMessageResponse.has("error"))
{
throw new SocialNetworkException(publishMessageResponse);
}
return publishMessageResponse.getString("id");
}
private void reloginPage(long uid) throws SQLException, SocialNetworkUnsupportedException {
String pageid = HSQLGameDatabase.getInstance().getSocialNetworkPageId("facebook", uid, gameinstid);
String pagetoken = HSQLGameDatabase.getInstance().getSocialNetworkPageToken("facebook", uid, gameinstid);
addClient(true, pageid, pagetoken);
}
/**
* take some time after upload to be processed by facebook
* @throws SQLException
* @throws SocialNetworkUnsupportedException
* @throws JSONException, IOException
* @throws SocialNetworkException
*/
@Override
public String publishOnFeedWithMedia(boolean publishOnPage, String type, String message, InputStream is, long uid)
throws NotImplementedException, MediaTypeNotSupportedException, SocialNetworkUnsupportedException, SQLException, JSONException, IOException, SocialNetworkException {
if (!(type.equalsIgnoreCase("videos") || type.equalsIgnoreCase("photos")))
throw new MediaTypeNotSupportedException(type);
if(publishOnPage && !isLoggedIn(publishOnPage, uid)) {
reloginPage(uid);
}
FBIdentities ident = getClient(publishOnPage, String.valueOf(uid));
FBMediaType mediatype = FBMediaType.valueOf(type);
JSONObject publishMessageResponse = FBPublishPredefs.executePublishLinkAndMediaMessage(ident, message, null, is, mediatype);
if(publishMessageResponse.has("error"))
{
throw new SocialNetworkException(publishMessageResponse);
}
return publishMessageResponse.has("post_id") ? publishMessageResponse.getString("post_id") : publishMessageResponse.getString("id");
}
@Override
public String publishOnFeedWithUrl(boolean publishOnPage, String message, long uid, String url) throws SocialNetworkUnsupportedException, SQLException, IOException, JSONException, SocialNetworkException {
if(publishOnPage && !isLoggedIn(publishOnPage, uid)) {
reloginPage(uid);
}
FBIdentities ident = getClient(publishOnPage, String.valueOf(uid));
JSONObject publishMessageResponse = FBPublishPredefs.executePublishLinkMessage(ident, message, url);
if(publishMessageResponse.has("error"))
{
throw new SocialNetworkException(publishMessageResponse);
}
return publishMessageResponse.getString("id");
}
@Override
public boolean deletePost(long uid, String postId) throws NotImplementedException, IOException, JSONException, SocialNetworkException {
JSONObject result = FBDeletePredefs.executeDeletePost(getClient(false, String.valueOf(uid)), postId);
if(result.has("error"))
{
throw new SocialNetworkException(result);
}
return result.getBoolean("success");
}
@Override
public boolean comment(long uid, String postID, String message) throws NotImplementedException, IOException, SocialNetworkException {
try {
JSONObject result = FBPublishPredefs.executePublishComment(getClient(false, String.valueOf(uid)), postID, message);
if(result.has("error"))
{
throw new SocialNetworkException(result);
}
return result.has("id");
} catch (com.restfb.exception.FacebookNetworkException e) {
return false;
}
}
@Override
public NetworkPost readPost(long uid, String postID) throws NotImplementedException,
PostNotAvailableException, JSONException, IOException, SocialNetworkException {
FBIdentities ident = getClient(false, String.valueOf(uid));
JSONObject result = FBReadPredefs.executeReadPost(ident, postID);
if(result.has("error"))
{
throw new SocialNetworkException(result);
}
if(!result.has("id"))
throw new PostNotAvailableException();
String from = result.getJSONObject("from").getString("name");
String fromid = result.getJSONObject("from").getString("id");
String msg = result.has("message") ? result.getString("message") : null;
String img = result.has("picture") ? result.getString("picture") : null;
long likecount = FBReadPredefs.executeReadLikes(ident, postID).getLong("count");
return new NetworkPost(fromid, from, msg, img, likecount);
}
@Override
public List<NetworkPost> readComments(long uid, String postId) throws NotImplementedException,
PostNotAvailableException, JSONException, IOException, SocialNetworkException {
List<NetworkPost> res = new LinkedList<NetworkPost>();
JSONObject result = FBReadPredefs.executeReadComments(getClient(false, String.valueOf(uid)), postId);
if(result.has("error"))
{
throw new SocialNetworkException(result);
}
if(!result.has("data"))
throw new PostNotAvailableException();
JSONArray comments = result.getJSONArray("data");
for(int i = 0; i < comments.length(); i++){
JSONObject comment = comments.getJSONObject(i);
String from = comment.getJSONObject("from").getString("name");
String fromId = comment.getJSONObject("from").getString("id");
String msg = comment.getString("message");
long likecount = comment.getLong("like_count");
res.add(new NetworkPost(fromId, from, msg, likecount));
}
return res;
}
@Override
public NetworkPostSupport getSupports(long uid, String postId) throws NotImplementedException,
PostNotAvailableException, JSONException, IOException, SocialNetworkException {
JSONObject result = FBReadPredefs.executeReadLikes(getClient(false, String.valueOf(uid)), postId);
if(result.has("error"))
{
throw new SocialNetworkException(result);
}
if(!result.has("count"))
throw new PostNotAvailableException();
NetworkPostSupport support = new NetworkPostSupport(result.getLong("count"));
for(int i = 0; i < result.getJSONArray("data").length(); i++) {
JSONObject like = result.getJSONArray("data").getJSONObject(i);
String id = like.getString("id");
String name = like.getString("name");
support.addSupporter(support.new PostSupporters(name, id));
}
return support;
}
@Override
public void getAttributes(long uid, String snuid, AttributeMap atts) throws NotImplementedException, IOException, JSONException {
JSONObject result = FBReadPredefs.executeReadUser(getClient(false, String.valueOf(uid)), snuid);
AttributeParser parser = new FacebookAttributeParser(atts);
parser.parseAttributes(result);
}
@Override
public String getPhotoThumbnailUrl(String snuid) throws NotImplementedException, IOException {
URL url = new URL("http://graph.facebook.com/" + snuid + "/picture?type=normal");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
String thumbUrl = conn.getURL().toString();
conn.disconnect();
return thumbUrl;
}
conn.disconnect();
throw new IOException();
}
/**
* @param uid
* @return the network-specific id of the user.
* @throws IOException
* @throws JSONException
*/
private String getUsersNetworkID(long uid) throws IOException, JSONException {
JSONObject user = FBReadPredefs.executeReadUser(getClient(false, String.valueOf(uid)));
return user.getString("id");
}
public static FacebookConnection getConnection(long gameinstid) throws NoSNConnectionException {
if(connectionPerGame == null)
connectionPerGame = new HashMap<Long, FacebookConnection>();
if(!connectionPerGame.containsKey(gameinstid)){
System.out.println("game instance not found");
connectionPerGame.put(gameinstid, new FacebookConnection(gameinstid));
}
return connectionPerGame.get(gameinstid);
}
}