package com.piusvelte.sonet.social; import android.app.Activity; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.res.Resources; import android.database.Cursor; import android.net.Uri; import android.support.annotation.DrawableRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; import android.util.Log; import com.piusvelte.okoauth.Helper; import com.piusvelte.sonet.BuildConfig; import com.piusvelte.sonet.R; import com.piusvelte.sonet.Sonet; import com.piusvelte.sonet.SonetCrypto; import com.piusvelte.sonet.SonetHttpClient; import com.piusvelte.sonet.provider.Entity; import com.piusvelte.sonet.provider.Notifications; import com.piusvelte.sonet.provider.StatusImages; import com.piusvelte.sonet.provider.StatusLinks; import com.piusvelte.sonet.provider.Statuses; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import static com.piusvelte.sonet.Sonet.Simgur; import static com.piusvelte.sonet.Sonet.Slink; import static com.piusvelte.sonet.Sonet.sRFC822; import static com.piusvelte.sonet.Sonet.sTimeZone; /** * Created by bemmanuel on 2/15/15. */ abstract public class Client { String mTag; Context mContext; String mToken; String mSecret; String mAccountEsid; int mNetwork; Helper mOAuth10Helper; private SimpleDateFormat mSimpleDateFormat = null; public Client(Context context, String token, String secret, String accountEsid, int network) { mTag = getClass().getSimpleName(); mContext = context; mToken = token; mSecret = secret; mAccountEsid = accountEsid; mNetwork = network; } public enum Network { Twitter { @Override public Client getClient(Context context, String token, String secret, String accountEntityId) { return new Twitter(context, token, secret, accountEntityId, this.ordinal()); } @Override public boolean isLocationSupported() { return true; } @Override public int getIcon() { return R.drawable.twitter; } }, Facebook { @Override public Client getClient(Context context, String token, String secret, String accountEntityId) { return new Facebook(context, token, secret, accountEntityId, this.ordinal()); } @Override public boolean isLocationSupported() { return true; } @Override public int getIcon() { return R.drawable.facebook; } }, MySpace { @Override public Client getClient(Context context, String token, String secret, String accountEntityId) { return new MySpace(context, token, secret, accountEntityId, this.ordinal()); } @Override public boolean isLocationSupported() { return false; } @Override public int getIcon() { return R.drawable.myspace; } }, Buzz { @Override public Client getClient(Context context, String token, String secret, String accountEntityId) { return null; } @Override public boolean isLocationSupported() { return false; } @Override public int getIcon() { return R.drawable.buzz; } }, Foursquare { @Override public Client getClient(Context context, String token, String secret, String accountEntityId) { return new Foursquare(context, token, secret, accountEntityId, this.ordinal()); } @Override public boolean isLocationSupported() { return true; } @Override public int getIcon() { return R.drawable.foursquare; } }, LinkedIn { @Override public Client getClient(Context context, String token, String secret, String accountEntityId) { return new LinkedIn(context, token, secret, accountEntityId, this.ordinal()); } @Override public boolean isLocationSupported() { return false; } @Override public int getIcon() { return R.drawable.linkedin; } }, Sms { @Override public Client getClient(Context context, String token, String secret, String accountEntityId) { throw new IllegalArgumentException("SMS is not a SocialClient"); } @Override public boolean isLocationSupported() { return false; } @Override public int getIcon() { return R.drawable.sms; } }, Rss { @Override public Client getClient(Context context, String token, String secret, String accountEntityId) { return new Rss(context, token, secret, accountEntityId, this.ordinal()); } @Override public boolean isLocationSupported() { return false; } @Override public int getIcon() { return R.drawable.rss; } }, IdentiCa { @Override public Client getClient(Context context, String token, String secret, String accountEntityId) { return new IdentiCa(context, token, secret, accountEntityId, this.ordinal()); } @Override public boolean isLocationSupported() { return false; } @Override public int getIcon() { return R.drawable.identica; } }, GooglePlus { @Override public Client getClient(Context context, String token, String secret, String accountEntityId) { return new GooglePlus(context, token, secret, accountEntityId, this.ordinal()); } @Override public boolean isLocationSupported() { return false; } @Override public int getIcon() { return R.drawable.googleplus; } }, Pinterest { @Override public Client getClient(Context context, String token, String secret, String accountEntityId) { return new Pinterest(context, token, secret, accountEntityId, this.ordinal()); } @Override public boolean isLocationSupported() { return false; } @Override public int getIcon() { return R.drawable.buzz;// TODO replace } }, Chatter { @Override public Client getClient(Context context, String token, String secret, String accountEntityId) { return new Chatter(context, token, secret, accountEntityId, this.ordinal()); } @Override public boolean isLocationSupported() { return false; } @Override public int getIcon() { return R.drawable.salesforce; } }; public static Network get(int network) { return Network.values()[network]; } abstract public Client getClient(Context context, String token, String secret, String accountEntityId); abstract public boolean isLocationSupported(); @DrawableRes abstract public int getIcon(); } public static class Builder { private Context mContext; private Network mNetwork; private String mToken; private String mSecret; private String mAccountEsid; public Builder(@NonNull Context context) { mContext = context.getApplicationContext(); } public static Builder from(@NonNull Client client) { return new Builder(client.mContext) .setNetwork(client.mNetwork) .setCredentials(client.mToken, client.mSecret) .setAccount(client.mAccountEsid); } public Builder setNetwork(Network network) { mNetwork = network; return this; } public Builder setNetwork(int network) { mNetwork = Network.get(network); return this; } public Builder setCredentials(String token, String secret) { mToken = token; mSecret = secret; return this; } public Builder setAccount(String accountEsid) { mAccountEsid = accountEsid; return this; } public Client build() { return mNetwork.getClient(mContext, mToken, mSecret, mAccountEsid); } } public String getFeed(int appWidgetId, String widget, long account, int status_count, boolean time24hr, boolean display_profile, int notifications) { String[] notificationMessage = new String[1]; Set<String> notificationSids = null; boolean doNotify = notifications != 0; if (doNotify) { notificationSids = getNotificationStatusIds(account, notificationMessage); } String response = getFeedResponse(status_count); if (!TextUtils.isEmpty(response)) { JSONArray feedItems; int parseCount; try { feedItems = parseFeed(response); if (feedItems != null) { parseCount = Math.min(feedItems.length(), status_count); if (parseCount > 0) { removeOldStatuses(widget, Long.toString(account)); for (int itemIdx = 0; itemIdx < parseCount; itemIdx++) { JSONObject item = feedItems.getJSONObject(itemIdx); if (item != null) { addFeedItem(item, display_profile, time24hr, appWidgetId, account, notificationSids, notificationMessage, doNotify); } } } else if (this instanceof MySpace) { // warn about myspace permissions addStatusItem(0, getString(R.string.myspace_permissions_title), null, getString(R.string.myspace_permissions_message), time24hr, appWidgetId, account, "", "", new ArrayList<String[]>() ); } } } catch (JSONException e) { if (BuildConfig.DEBUG) { Log.d(mTag, "error parsing feed response: " + response, e); } } } else if (this instanceof MySpace) { // warn about myspace permissions addStatusItem(0, getString(R.string.myspace_permissions_title), null, getString(R.string.myspace_permissions_message), time24hr, appWidgetId, account, "", "", new ArrayList<String[]>() ); } if (doNotify) { getNotificationMessage(account, notificationMessage); return notificationMessage[0]; } return null; } @Nullable abstract public String getProfileUrl(@NonNull String esid); @Nullable abstract public String getProfilePhotoUrl(); @Nullable abstract public String getProfilePhotoUrl(String esid); @Nullable abstract public Set<String> getNotificationStatusIds(long account, String[] notificationMessage); @Nullable abstract public String getFeedResponse(int status_count); @Nullable abstract public JSONArray parseFeed(@NonNull String response) throws JSONException; @Nullable abstract public void addFeedItem(@NonNull JSONObject item, boolean display_profile, boolean time24hr, int appWidgetId, long account, Set<String> notificationSids, String[] notificationMessage, boolean doNotify) throws JSONException; @Nullable abstract public void getNotificationMessage(long account, String[] notificationMessage); abstract public void getNotifications(long account, String[] notificationMessage); abstract public boolean createPost(String message, String placeId, String latitude, String longitude, String photoPath, String[] tags); abstract public boolean isLikeable(String statusId); abstract public boolean isLiked(String statusId, String accountId); abstract public boolean likeStatus(String statusId, String accountId, boolean doLike); abstract public String getLikeText(boolean isLiked); abstract public boolean isCommentable(String statusId); abstract public String getCommentPretext(String accountId); abstract public void onDelete(); public List<HashMap<String, String>> getComments(@NonNull String statusId, boolean time24hr) { List<HashMap<String, String>> parsedComments = new ArrayList<>(); String response = getCommentsResponse(statusId); if (!TextUtils.isEmpty(response)) { JSONArray jsonComments = null; try { jsonComments = parseComments(response); } catch (JSONException e) { if (BuildConfig.DEBUG) Log.d(mTag, "exception parsing: " + response, e); } if (jsonComments != null) { for (int commentsIdx = 0, commentsLength = jsonComments.length(); commentsIdx < commentsLength; commentsIdx++) { JSONObject comment = null; try { comment = jsonComments.getJSONObject(commentsIdx); } catch (JSONException e) { if (BuildConfig.DEBUG) Log.d(mTag, "exception getting comment", e); } if (comment != null) { HashMap<String, String> parsedComment = null; try { parsedComment = parseComment(statusId, comment, time24hr); } catch (JSONException e) { if (BuildConfig.DEBUG) Log.d(mTag, "exception parsing comment", e); } if (parsedComment != null) { parsedComments.add(parsedComment); } } } } } return parsedComments; } @Nullable abstract public String getCommentsResponse(String statusId); @Nullable abstract public JSONArray parseComments(@NonNull String response) throws JSONException; @Nullable abstract public HashMap<String, String> parseComment(@NonNull String statusId, @NonNull JSONObject jsonComment, boolean time24hr) throws JSONException; abstract public LinkedHashMap<String, String> getLocations(String latitude, String longitude); abstract public boolean sendComment(@NonNull String statusId, @NonNull String message); abstract public List<HashMap<String, String>> getFriends(); abstract String getApiKey(); abstract String getApiSecret(); @Nullable abstract public Uri getCallback(); abstract String getRequestUrl(); abstract String getAccessUrl(); abstract String getAuthorizeUrl(); abstract public String getCallbackUrl(); abstract public MemberAuthentication getMemberAuthentication(@NonNull String authenticatedUrl); public boolean hasConnectionError() { return false; } /** * Attempt to resolve a connection error {@link #hasConnectionError()} * * @param activity * @param requestCode * @return {@code True} if resolution is attempted */ public boolean resolveConnectionError(@NonNull Activity activity, int requestCode) { return false; } @Nullable public static String getParamValue(@Nullable String url, @NonNull String name) { if (TextUtils.isEmpty(url)) return null; name += "="; int nameIndex = url.indexOf(name); if (nameIndex < 0) return null; String value = url.substring(nameIndex + name.length()); int nextParamIndex = value.indexOf("&"); if (nextParamIndex >= 0) { value = value.substring(0, nextParamIndex); } return value; } @Nullable public String getAuthUrl() { return getOAuth10Helper().getTokenAuthorizationUrl(SonetHttpClient.getOkHttpClientInstance()); } Helper getOAuth10Helper() { if (mOAuth10Helper == null) { mOAuth10Helper = new Helper(getApiKey(), getApiSecret(), mToken, mSecret, getRequestUrl(), getAuthorizeUrl(), getAccessUrl(), getCallbackUrl()); } return mOAuth10Helper; } String getString(int resId) { return mContext.getString(resId); } Resources getResources() { return mContext.getResources(); } ContentResolver getContentResolver() { return mContext.getContentResolver(); } String getFirstPhotoUrl(String[] parts) { if (parts.length > 1) { Uri uri = Uri.parse(parts[1]); if (uri.getHost().equals(Simgur)) { return parts[1]; } } return null; } String getPostFriendOverride(String friend) { return null; } String getPostFriend(String friend) { return friend; } void formatLink(Matcher matcher, StringBuffer stringBuffer, String link) { matcher.appendReplacement(stringBuffer, "(" + Slink + ": " + Uri.parse(link).getHost() + ")"); } void addStatusItem(long created, String friend, String url, String message, boolean time24hr, int appWidgetId, long accountId, String sid, String esid) { addStatusItem(created, friend, url, message, time24hr, appWidgetId, accountId, sid, esid, new ArrayList<String[]>()); } void addStatusItem(long created, String friend, String url, String message, boolean time24hr, int appWidgetId, long accountId, String sid, String esid, ArrayList<String[]> links) { long id; String friend_override = getPostFriendOverride(friend); friend = getPostFriend(friend); ContentValues entityValues = new ContentValues(); entityValues.put(Entity.FRIEND, friend); entityValues.put(Entity.PROFILE_URL, url); Cursor entity = getContentResolver().query(Entity.getContentUri(mContext), new String[] { Entity._ID }, Entity.ACCOUNT + "=? and " + Entity.ESID + "=?", new String[] { Long.toString(accountId), SonetCrypto.getInstance(mContext).Encrypt(esid) }, null); if (entity.moveToFirst()) { id = entity.getLong(0); // update friend and profile_url if changed getContentResolver().update(Entity.getContentUri(mContext), entityValues, Entity.ACCOUNT + "=? and " + Entity.ESID + "=?", new String[] { Long.toString(accountId), SonetCrypto.getInstance(mContext).Encrypt(esid) }); } else { entityValues.put(Entity.ESID, esid); entityValues.put(Entity.ACCOUNT, accountId); id = Long.parseLong(getContentResolver().insert(Entity.getContentUri(mContext), entityValues).getLastPathSegment()); } entity.close(); // facebook sid comes in as esid_sid, the esid_ may need to be removed // if (serviceId == FACEBOOK) { // int split = sid.indexOf("_"); // if ((split > 0) && (split < sid.length())) { // sid = sid.substring(sid.indexOf("_") + 1); // } // } // update the account statuses // parse any links Matcher m = Pattern.compile("\\bhttp(s)?://\\S+\\b", Pattern.CASE_INSENSITIVE).matcher(message); StringBuffer sb = new StringBuffer(message.length()); while (m.find()) { String link = m.group(); // check existing links before adding boolean exists = false; for (String[] l : links) { if (l[1].equals(link)) { exists = true; break; } } if (!exists) { links.add(new String[] { Slink, link }); formatLink(m, sb, link); } } m.appendTail(sb); message = sb.toString(); ContentValues values = new ContentValues(); values.put(Statuses.CREATED, created); values.put(Statuses.ENTITY, id); values.put(Statuses.MESSAGE, message); values.put(Statuses.SERVICE, mNetwork); values.put(Statuses.CREATEDTEXT, Sonet.getCreatedText(created, time24hr)); values.put(Statuses.WIDGET, appWidgetId); values.put(Statuses.ACCOUNT, accountId); values.put(Statuses.SID, sid); values.put(Statuses.FRIEND_OVERRIDE, friend_override); long statusId = Long.parseLong(getContentResolver().insert(Statuses.getContentUri(mContext), values).getLastPathSegment()); String imageUrl = null; for (String[] s : links) { // get the first photo if (imageUrl == null) { imageUrl = getFirstPhotoUrl(s); } ContentValues linkValues = new ContentValues(); linkValues.put(StatusLinks.STATUS_ID, statusId); linkValues.put(StatusLinks.LINK_TYPE, s[0]); linkValues.put(StatusLinks.LINK_URI, s[1]); getContentResolver().insert(StatusLinks.getContentUri(mContext), linkValues); } if (!TextUtils.isEmpty(imageUrl)) { addStatusImage(statusId, imageUrl); } } private void addStatusImage(long statusId, @NonNull String imageUrl) { ContentValues imageValues = new ContentValues(); imageValues.put(StatusImages.STATUS_ID, statusId); imageValues.put(StatusImages.URL, imageUrl); mContext.getContentResolver().insert(StatusImages.getContentUri(mContext), imageValues); } void removeOldStatuses(String widgetId, String accountId) { Cursor statuses = getContentResolver() .query(Statuses.getContentUri(mContext), new String[] { Statuses._ID }, Statuses.WIDGET + "=? and " + Statuses.ACCOUNT + "=?", new String[] { widgetId, accountId }, null); if (statuses.moveToFirst()) { while (!statuses.isAfterLast()) { String id = Long.toString(statuses.getLong(0)); getContentResolver().delete(StatusLinks.getContentUri(mContext), StatusLinks.STATUS_ID + "=?", new String[] { id }); getContentResolver().delete(StatusImages.getContentUri(mContext), StatusImages.STATUS_ID + "=?", new String[] { id }); statuses.moveToNext(); } } statuses.close(); getContentResolver().delete(Statuses.getContentUri(mContext), Statuses.WIDGET + "=? and " + Statuses.ACCOUNT + "=?", new String[] { widgetId, accountId }); Cursor entities = getContentResolver() .query(Entity.getContentUri(mContext), new String[] { Entity._ID }, Entity.ACCOUNT + "=?", new String[] { accountId }, null); if (entities.moveToFirst()) { while (!entities.isAfterLast()) { Cursor s = getContentResolver().query(Statuses.getContentUri(mContext), new String[] { Statuses._ID }, Statuses.ACCOUNT + "=? and " + Statuses.WIDGET + " !=?", new String[] { accountId, widgetId }, null); if (!s.moveToFirst()) { // not in use, remove it getContentResolver() .delete(Entity.getContentUri(mContext), Entity._ID + "=?", new String[] { Long.toString(entities.getLong(0)) }); } s.close(); entities.moveToNext(); } } entities.close(); } void addNotification(String sid, String esid, String friend, String message, long created, long accountId, String notification) { ContentValues values = new ContentValues(); values.put(Notifications.SID, sid); values.put(Notifications.ESID, esid); values.put(Notifications.FRIEND, friend); values.put(Notifications.MESSAGE, message); values.put(Notifications.CREATED, created); values.put(Notifications.ACCOUNT, accountId); values.put(Notifications.NOTIFICATION, notification); values.put(Notifications.CLEARED, 0); values.put(Notifications.UPDATED, created); getContentResolver().insert(Notifications.getContentUri(mContext), values); } String updateNotification(long notificationId, long created_time, String accountEsid, String esid, String name, boolean cleared) { String message = null; // new comment ContentValues values = new ContentValues(); values.put(Notifications.UPDATED, created_time); if (accountEsid.equals(esid)) { // user's own comment, clear the notification values.put(Notifications.CLEARED, 1); } else if (cleared) { values.put(Notifications.NOTIFICATION, String.format(getString(R.string.friendcommented), name)); values.put(Notifications.CLEARED, 0); message = String.format(getString(R.string.friendcommented), name); } else { values.put(Notifications.NOTIFICATION, String.format(getString(R.string.friendcommented), name + " and others")); message = String.format(getString(R.string.friendcommented), name + " and others"); } getContentResolver().update(Notifications.getContentUri(mContext), values, Notifications._ID + "=?", new String[] { Long.toString(notificationId) }); return message; } void updateNotificationMessage(String[] originalMessage, String newMessage) { if (TextUtils.isEmpty(originalMessage[0])) { originalMessage[0] = newMessage; } else if (!TextUtils.isEmpty(newMessage)) { originalMessage[0] = mContext.getString(R.string.notify_multiple_updates); } } long parseDate(String date, String format) { if (date != null) { // hack for the literal 'Z' if (date.substring(date.length() - 1).equals("Z")) { date = date.substring(0, date.length() - 2) + "+0000"; } Date created = null; if (format != null) { if (mSimpleDateFormat == null) { mSimpleDateFormat = new SimpleDateFormat(format, Locale.ENGLISH); // all dates should be GMT/UTC mSimpleDateFormat.setTimeZone(sTimeZone); } try { created = mSimpleDateFormat.parse(date); return created.getTime(); } catch (ParseException e) { Log.e(mTag, e.toString()); } } else { // attempt to parse RSS date if (mSimpleDateFormat != null) { try { created = mSimpleDateFormat.parse(date); return created.getTime(); } catch (ParseException e) { Log.e(mTag, e.toString()); } } for (String rfc822 : sRFC822) { mSimpleDateFormat = new SimpleDateFormat(rfc822, Locale.ENGLISH); mSimpleDateFormat.setTimeZone(sTimeZone); try { if ((created = mSimpleDateFormat.parse(date)) != null) { return created.getTime(); } } catch (ParseException e) { Log.e(mTag, e.toString()); } } } } return System.currentTimeMillis(); } public static class MemberAuthentication { public String username; public String token; public String secret; public int expiry; public int network; public String id; } }