/*
* Copyright (C) 2015 8tory, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.sina.weibo.simple;
import java.util.Set;
import java.util.List;
import java.util.HashSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import com.sina.weibo.sdk.auth.*;
import com.sina.weibo.sdk.auth.sso.*;
import com.sina.weibo.sdk.exception.*;
import com.sina.weibo.sdk.api.*;
import com.sina.weibo.sdk.api.share.*;
import com.sina.weibo.sdk.constant.WBConstants;
import android.os.Bundle;
import android.text.TextUtils;
import android.content.Intent;
import android.content.Context;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.content.pm.ApplicationInfo;
import rx.Observable;
import rx.functions.*;
import rx.Subscriber;
import rx.subjects.Subject;
import rx.subjects.PublishSubject;
import retroweibo.RetroWeibo;
import android.graphics.Bitmap;
@RetroWeibo
public abstract class SimpleWeibo {
@RetroWeibo.GET("/statuses/friends_timeline.json")
public abstract Observable<Status> getFriendStatuses(
@RetroWeibo.Query("since_id") long sinceId,
@RetroWeibo.Query("max_id") long maxId,
@RetroWeibo.Query("base_app") boolean baseApp,
@RetroWeibo.Query("trim_user") boolean trimUser,
@RetroWeibo.Query("feature") int featureType
);
@RetroWeibo.GET("/statuses/user_timeline.json")
public abstract Observable<Status> getStatuses(
@RetroWeibo.Query("since_id") long sinceId,
@RetroWeibo.Query("max_id") long maxId,
@RetroWeibo.Query("base_app") boolean baseApp,
@RetroWeibo.Query("trim_user") boolean trimUser,
@RetroWeibo.Query("feature") int featureType
);
public Observable<Status> getStatuses() {
return getStatuses(
UNKNOWN_SINCE_ID,
UNKNOWN_MAX_ID,
NOT_APP_ONLY,
NOT_TRIM_USER,
FEATURE_ALL
);
}
public Observable<Status> getPictureStatuses() {
return getStatuses(
UNKNOWN_SINCE_ID,
UNKNOWN_MAX_ID,
NOT_APP_ONLY,
NOT_TRIM_USER,
FEATURE_PICTURE
);
}
@RetroWeibo.GET("/statuses/mentions.json")
public abstract Observable<Status> getMentionedStatuses(
@RetroWeibo.Query("since_id") long sinceId,
@RetroWeibo.Query("max_id") long maxId,
@RetroWeibo.Query("filter_by_author") int filterByAuthor,
@RetroWeibo.Query("filter_by_source") int filterBySource,
@RetroWeibo.Query("filter_by_type") int filterByType,
@RetroWeibo.Query("trim_user") boolean trimUser
);
public Observable<Status> getMentionedStatuses() {
return getMentionedStatuses(UNKNOWN_SINCE_ID,
UNKNOWN_MAX_ID,
AUTHOR_FILTER_ALL,
SRC_FILTER_ALL,
TYPE_FILTER_ALL,
NOT_TRIM_USER
);
}
@RetroWeibo.GET("/users/show.json")
public abstract Observable<User> getUsersById(@RetroWeibo.Query("uid") long uid);
@RetroWeibo.GET("/users/show.json")
public abstract Observable<User> getUsersByName(@RetroWeibo.Query("screen_name") String screenName);
@RetroWeibo.GET("/users/domain_show.json")
public abstract Observable<User> getUsersByDomain(@RetroWeibo.Query("domain") String domain);
@RetroWeibo.GET("/users/counts.json")
public abstract Observable<User> getUsersCount(@RetroWeibo.Query("uids") long[] uids);
@RetroWeibo.GET("/comments/show.json")
public abstract Observable<Comment> getCommentsById(
@RetroWeibo.Query("id") int id,
@RetroWeibo.Query("since_id") long sinceId,
@RetroWeibo.Query("max_id") long maxId,
@RetroWeibo.Query("count") int count,
@RetroWeibo.Query("page") int page,
@RetroWeibo.Query("filter_by_author") int filterByAuthor
);
public Observable<Comment> getCommentsById(int id) {
return getCommentsById(
id,
UNKNOWN_SINCE_ID,
UNKNOWN_MAX_ID,
DEFAULT_LIMIT,
FRONT_PAGE,
AUTHOR_FILTER_ALL
);
}
@RetroWeibo.GET("/comments/by_me.json")
public abstract Observable<Comment> getCommentsByMe(
@RetroWeibo.Query("since_id") long sinceId,
@RetroWeibo.Query("max_id") long maxId,
@RetroWeibo.Query("count") int count,
@RetroWeibo.Query("page") int page,
@RetroWeibo.Query("filter_by_source") int filterBySource
);
public Observable<Comment> getCommentsByMe() {
return getCommentsByMe(SRC_FILTER_ALL);
}
public Observable<Comment> getCommentsByMe(int filterBySource) {
return getCommentsByMe(
UNKNOWN_SINCE_ID,
UNKNOWN_MAX_ID,
DEFAULT_LIMIT,
FRONT_PAGE,
filterBySource
);
}
@RetroWeibo.GET("/comments/to_me.json")
public abstract Observable<Comment> getCommentsToMe(
@RetroWeibo.Query("since_id") long sinceId,
@RetroWeibo.Query("max_id") long maxId,
@RetroWeibo.Query("count") int count,
@RetroWeibo.Query("page") int page,
@RetroWeibo.Query("filter_by_author") int filterByAuthor,
@RetroWeibo.Query("filter_by_source") int filterBySource
);
public Observable<Comment> getCommentsToMe() {
return getCommentsToMe(AUTHOR_FILTER_ALL, SRC_FILTER_ALL);
}
public Observable<Comment> getCommentsToMe(int filterByAuthor, int filterBySource) {
return getCommentsToMe(
UNKNOWN_SINCE_ID,
UNKNOWN_MAX_ID,
DEFAULT_LIMIT,
FRONT_PAGE,
filterByAuthor,
filterBySource
);
}
@RetroWeibo.GET("/comments/timeline.json")
public abstract Observable<Comment> getComments(
@RetroWeibo.Query("since_id") long sinceId,
@RetroWeibo.Query("max_id") long maxId,
@RetroWeibo.Query("count") int count,
@RetroWeibo.Query("page") int page,
@RetroWeibo.Query("trim_user") boolean trimUser
);
public Observable<Comment> getComments() {
return getComments(NOT_TRIM_USER);
}
public Observable<Comment> getComments(boolean trimUser) {
return getComments(
UNKNOWN_SINCE_ID,
UNKNOWN_MAX_ID,
DEFAULT_LIMIT,
FRONT_PAGE,
trimUser
);
}
@RetroWeibo.GET("/comments/mentions.json")
public abstract Observable<Comment> getMentionedComments(
@RetroWeibo.Query("since_id") long sinceId,
@RetroWeibo.Query("max_id") long maxId,
@RetroWeibo.Query("count") int count,
@RetroWeibo.Query("page") int page,
@RetroWeibo.Query("filter_by_author") int filterByAuthor,
@RetroWeibo.Query("filter_by_source") int filterBySource
);
public Observable<Comment> getMentionedComments() {
return getMentionedComments(AUTHOR_FILTER_ALL, SRC_FILTER_ALL);
}
public Observable<Comment> getMentionedComments(int filterByAuthor, int filterBySource) {
return getMentionedComments(
UNKNOWN_SINCE_ID,
UNKNOWN_MAX_ID,
DEFAULT_LIMIT,
FRONT_PAGE,
filterByAuthor,
filterBySource
);
}
@RetroWeibo.GET("/comments/show_batch.json")
public abstract Observable<Comment> getComments(@RetroWeibo.Query("cids") long[] commentIds);
//@RetroWeibo.POST("/messages/invite.json")
@RetroWeibo.POST("https://m.api.weibo.com/2/messages/invite.json")
//public abstract Observable<Response> invite(@RetroWeibo.Query("uid") long uid, @RetroWeibo.Body("data") Invitation invitation);
public abstract Observable<Response> invite(@RetroWeibo.Query("uid") long uid, @RetroWeibo.Query("data") Invitation invitation);
/*
@RetroWeibo.POST("/statuses/update.json")
public abstract Observable<Response> publishStatus(
@RetroWeibo.Query("status") String content,
@RetroWeibo.Query("long") String longtitude,
@RetroWeibo.Query("lat") String latitude
);
*/
/**
* @param source (optional) String 采用OAuth授权方式不需要此参数,其他授权方式为必填参数,数值为应用的AppKey。
* @param access_token (optional) String 采用OAuth授权方式为必填参数,其他授权方式不需要此参数,OAuth授权后获得。
* @param status (required) String 要发布的微博文本内容,必须做URLencode,内容不超过140个汉字。
* @param visible (optional) int 微博的可见性,0:所有人能看,1:仅自己可见,2:密友可见,3:指定分组可见,默认为0。
* @param list_id (optional) String 微博的保护投递指定分组ID,只有当visible参数为3时生效且必选。
* @param lat (optional) float 纬度,有效范围:-90.0到+90.0,+表示北纬,默认为0.0。
* @param long (optional) float 经度,有效范围:-180.0到+180.0,+表示东经,默认为0.0。
* @param annotations (optional) string 元数据,主要是为了方便第三方应用记录一些适合于自己使用的信息,每条微博可以包含一个或者多个元数据,必须以json字串的形式提交,字串长度不超过512个字符,具体内容可以自定。
* @param rip (optional) String 开发者上报的操作用户真实IP,形如:211.156.0.1。
* @see http://open.weibo.com/wiki/2/statuses/update
*/
@RetroWeibo.POST("/statuses/update.json")
public abstract Observable<Status> publishStatus(
@RetroWeibo.Query("status") String content
);
@RetroWeibo.POST("/statuses/update.json")
public abstract Observable<Status> publishStatus(
@RetroWeibo.Query("status") String content,
@RetroWeibo.Query("long") double longtitude,
@RetroWeibo.Query("lat") double latitude
);
/**
* 發表圖片貼文.
*
* 類似 update ,僅多一項 Bitmap.
*
* @param source (optional) String 采用OAuth授权方式不需要此参数,其他授权方式为必填参数,数值为应用的AppKey。
* @param access_token (optional) String 采用OAuth授权方式为必填参数,其他授权方式不需要此参数,OAuth授权后获得。
* @param status (required) String 要发布的微博文本内容,必须做URLencode,内容不超过140个汉字。
* @param visible (optional) int 微博的可见性,0:所有人能看,1:仅自己可见,2:密友可见,3:指定分组可见,默认为0。
* @param list_id (optional) String 微博的保护投递指定分组ID,只有当visible参数为3时生效且必选。
* @param pic (required) Bitmap 要上传的图片,仅支持JPEG、GIF、PNG格式,图片大小小于5M。
* @param lat (optional) float 纬度,有效范围:-90.0到+90.0,+表示北纬,默认为0.0。
* @param long (optional) float 经度,有效范围:-180.0到+180.0,+表示东经,默认为0.0。
* @param annotations (optional) string 元数据,主要是为了方便第三方应用记录一些适合于自己使用的信息,每条微博可以包含一个或者多个元数据,必须以json字串的形式提交,字串长度不超过512个字符,具体内容可以自定。
* @param rip (optional) String 开发者上报的操作用户真实IP,形如:211.156.0.1。
*
* @see http://open.weibo.com/wiki/2/statuses/upload
*/
@RetroWeibo.POST("/statuses/upload.json")
public abstract Observable<Status> publishPhotoStatus(
@RetroWeibo.Query("status") String content,
@RetroWeibo.Query("pic") Bitmap picture
);
@RetroWeibo.POST("/statuses/upload.json")
public abstract Observable<Status> publishPhotoStatus(
@RetroWeibo.Query("status") String content,
@RetroWeibo.Query("pic") Bitmap picture,
@RetroWeibo.Query("long") double longtitude,
@RetroWeibo.Query("lat") double latitude
);
/**
* High-level API
*/
@RetroWeibo.POST("/statuses/upload_url_text.json")
public abstract Observable<Status> publishPhotoUrlStatus(
@RetroWeibo.Query("status") String content,
@RetroWeibo.Query("url") String pictureUrl,
@RetroWeibo.Query("pic_id") String pictureId,
@RetroWeibo.Query("long") double longtitude,
@RetroWeibo.Query("lat") double latitude
);
public Observable<Status> publishPhotoUrlStatus(
String content,
String pictureUrl,
double longtitude,
double latitude
) {
return publishPhotoUrlStatus(content, pictureUrl, null, longtitude, latitude);
}
@RetroWeibo.POST("/comments/create.json")
public abstract Observable<Comment> publishComment(
@RetroWeibo.Query("comment") String comment,
@RetroWeibo.Query("id") long id,
@RetroWeibo.Query("comment_ori") boolean pingback
);
public Observable<Comment> publishComment(String comment, long id) {
return publishComment(comment, id, true);
}
public Observable<Comment> publishComment(String comment, Status status) {
return publishComment(comment, status.id());
}
public Observable<Comment> publishComment(String comment, String id) {
return publishComment(comment, Long.valueOf(id));
}
@RetroWeibo.POST("/comments/destroy.json")
public abstract Observable<Comment> deleteComment(
@RetroWeibo.Query("cid") long commentId
);
@RetroWeibo.POST("/comments/sdestroy_batch.json")
public abstract Observable<Comment> deleteComments(
@RetroWeibo.Query("cids") long[] commentIds
);
@RetroWeibo.POST("/comments/reply.json")
public abstract Observable<Comment> replyComment(
@RetroWeibo.Query("comment") String comment,
@RetroWeibo.Query("cid") long cid,
@RetroWeibo.Query("id") long id,
@RetroWeibo.Query("without_mention") boolean withoutMention,
@RetroWeibo.Query("comment_ori") boolean pingback
);
public Observable<Comment> replyComment(
String comment,
long cid,
long id
) {
return replyComment(
comment,
cid,
id,
false,
true
);
}
public Observable<Comment> replyComment(String comment, Comment parentComment) {
return replyComment(comment, parentComment.id(), parentComment.status().id());
}
@RetroWeibo.POST("/oauth2/revokeoauth2")
public abstract Observable<Response> revoke();
public Observable<Response> logOut() {
return revoke();
}
private Activity activity;
private Context context;
private String appId;
private String redirectUrl;
private SsoHandler ssoHandler;
private static SimpleWeibo self;
private AccessToken accessToken;
private AuthInfo authInfo;
public static final String APPLICATION_ID_PROPERTY = "com.sina.weibo.sdk.ApplicationId";
public static final String REDIRECT_URL_PROPERTY = "com.sina.weibo.sdk.RedirectUrl";
private static final String DEFAULT_REDIRECT_URL = "https://api.weibo.com/oauth2/default.html";
public static SimpleWeibo get() {
return self;
}
public static SimpleWeibo create(Activity activity) {
self = new RetroWeibo_SimpleWeibo(activity).initialize(activity);
return self;
}
public SimpleWeibo initialize(Activity activity) {
this.activity = activity;
this.context = activity;
this.appId = getMetaData(activity, APPLICATION_ID_PROPERTY);
// null ? throw new IllegalArgumentException()
this.redirectUrl = getMetaData(activity, REDIRECT_URL_PROPERTY);
if (this.redirectUrl == null || "".equals(this.redirectUrl)) this.redirectUrl = DEFAULT_REDIRECT_URL;
this.authInfo = new AuthInfo(context, appId, redirectUrl, TextUtils.join(",", Arrays.asList("email")));
this.accessToken = AccessTokenPreferences.create(activity);
ssoHandler = new SsoHandler(activity, authInfo);
return this;
}
public String getAppId() {
return appId;
}
public AuthInfo getAuthInfo() {
return authInfo;
}
public Observable<AccessToken> logIn() {
return logInWithPermissions("email",
"direct_messages_read",
"direct_messages_write",
"friendships_groups_read",
"friendships_groups_write",
"statuses_to_me_read",
"follow_app_official_microblog",
"invitation_write"
);
}
public Observable<AccessToken> logInWithPermissions(String... permissions) {
return logInWithPermissions(Arrays.asList(permissions));
}
public boolean isLogin() {
if (accessToken == null) accessToken = AccessTokenPreferences.create(context);
return accessToken.token() != null && !"".equals(accessToken.token());
}
public Observable<AccessToken> logInWithPermissions(final Collection<String> permissions) {
if (accessToken == null) accessToken = AccessTokenPreferences.create(context);
if (isValid(accessToken)) {
if (!hasNewPermissions(accessToken, permissions)) {
return Observable.just(accessToken);
}
}
authInfo = new AuthInfo(context, appId, redirectUrl,
TextUtils.join(",", permissions));
ssoHandler = new SsoHandler(activity, authInfo);
return logInForOauth2AccessToken(permissions).map(new Func1<Oauth2AccessToken, AccessToken>() {
@Override public AccessToken call(Oauth2AccessToken oauth2) {
accessToken.uid(oauth2.getUid());
accessToken.token(oauth2.getToken());
accessToken.refreshToken(oauth2.getRefreshToken());
accessToken.expiresTime(oauth2.getExpiresTime());
accessToken.phoneNum(oauth2.getPhoneNum());
accessToken.permissions(new HashSet<>(permissions)); //accessToken.permissions(permissions.addAll(accessToken.permissions()));
return accessToken;
}
});
}
public Observable<Oauth2AccessToken> logInForOauth2AccessToken(Collection<String> permissions) {
return logInForBundle(permissions).map(new Func1<Bundle, Oauth2AccessToken>() {
@Override public Oauth2AccessToken call(Bundle bundle) {
return Oauth2AccessToken.parseAccessToken(bundle);
}
}).flatMap(new Func1<Oauth2AccessToken, Observable<Oauth2AccessToken>>() {
@Override public Observable<Oauth2AccessToken> call(Oauth2AccessToken oauth2) {
if (!oauth2.isSessionValid()) {
return Observable.error(new WeiboException("AccessToken is invalid"));
}
return Observable.just(oauth2);
}
});
}
public Observable<Bundle> logInForBundle(Collection<String> permissions) {
return Observable.create(new Observable.OnSubscribe<Bundle>() {
@Override public void call(final Subscriber<? super Bundle> sub) {
ssoHandler.authorize(new WeiboAuthListener() {
@Override public void onComplete(Bundle values) {
sub.onNext(values);
sub.onCompleted();
}
@Override public void onCancel() {
sub.onCompleted();
}
@Override public void onWeiboException(WeiboException e) {
sub.onError(e);
}
});
}
});
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
ssoHandler.authorizeCallBack(requestCode, resultCode, data);
}
public static boolean isValid(AccessToken accessToken) {
return !TextUtils.isEmpty(accessToken.token()); // TODO && isExpired(accessToken)
}
public static boolean hasNewPermissions(AccessToken accessToken, Collection<String> permissions) {
List<String> newPermissions = new ArrayList<>(permissions);
newPermissions.removeAll(accessToken.permissions());
return !newPermissions.isEmpty();
}
public static String getMetaData(Context context, String key) {
ApplicationInfo ai = null;
try {
ai = context.getPackageManager().getApplicationInfo(
context.getPackageName(), PackageManager.GET_META_DATA);
} catch (PackageManager.NameNotFoundException e) {
return null;
}
if (ai == null || ai.metaData == null) {
return null;
}
return ai.metaData.getString(key);
}
/** TODO enum */
public static final int FEATURE_ALL = 0;
public static final int FEATURE_ORIGINAL = 1;
public static final int FEATURE_PICTURE = 2;
public static final int FEATURE_VIDEO = 3;
public static final int FEATURE_MUSICE = 4;
public static final int AUTHOR_FILTER_ALL = 0;
public static final int AUTHOR_FILTER_ATTENTIONS = 1;
public static final int AUTHOR_FILTER_STRANGER = 2;
public static final int SRC_FILTER_ALL = 0;
public static final int SRC_FILTER_WEIBO = 1;
public static final int SRC_FILTER_WEIQUN = 2;
public static final int TYPE_FILTER_ALL = 0;
public static final int TYPE_FILTER_ORIGAL = 1;
public static final boolean APP_ONLY = true;
public static final boolean NOT_APP_ONLY = !APP_ONLY;
public static final boolean TRIM_USER = true;
public static final boolean NOT_TRIM_USER = !TRIM_USER;
public static final int FRONT_PAGE = 1;
public static final int DEFAULT_LIMIT = 32;
public static final long UNKNOWN_MAX_ID = 0L;
public static final long UNKNOWN_SINCE_ID = 0L;
private IWeiboShareAPI mWeiboShareAPI;
private Subject<BaseResponse, BaseResponse> shareSubject;
/**
* onCreate(Activity activity, Response response, Bundle savedInstanceState)
* onNewIntent(Response response, Intent intent)
* onResponse(BaseResponse baseResponse)
*/
// FIXME synchronized mWeiboShareAPI
public synchronized Observable<BaseResponse> share(final Activity activity, String text, Bitmap bitmap) {
TextObject textObject = new TextObject();
textObject.text = text;
WeiboMultiMessage weiboMessage = new WeiboMultiMessage();
weiboMessage.textObject = textObject;
if (bitmap != null) {
ImageObject imageObject = new ImageObject();
imageObject.setImageObject(bitmap);
weiboMessage.imageObject = imageObject;
}
final SendMultiMessageToWeiboRequest request = new SendMultiMessageToWeiboRequest();
request.transaction = String.valueOf(System.currentTimeMillis());
request.multiMessage = weiboMessage;
/*
mWeiboShareAPI.sendRequest(activity, request); // FIXME Should return shareObs before sendRequest()
*/
return Observable.defer(new Func0<Observable<BaseResponse>>() {
@Override public Observable<BaseResponse> call() {
shareSubject = PublishSubject.create();
mWeiboShareAPI.sendRequest(activity, request, authInfo, accessToken.token(), new WeiboAuthListener() {
@Override public void onComplete(Bundle values) {
android.util.Log.d("SimpleWeibo", "values: " + values);
}
@Override public void onCancel() {
android.util.Log.d("SimpleWeibo", "onCancel");
}
@Override public void onWeiboException(WeiboException e) {
android.util.Log.e("SimpleWeibo", "onError");
e.printStackTrace();
}
});
return shareSubject.asObservable();
}
});
}
// FIXME synchronized mWeiboShareAPI
public synchronized void onNewIntent(IWeiboHandler.Response response, Intent intent) {
mWeiboShareAPI.handleWeiboResponse(intent, response);
}
/**
* WBConstants.ErrorCode.ERR_OK
* WBConstants.ErrorCode.ERR_CANCEL
* WBConstants.ErrorCode.ERR_FAIL
*
* @param BaseResponse baseResponse baseResponse.errCode
*/
public void onResponse(BaseResponse baseResponse) {
if (baseResponse.errCode == WBConstants.ErrorCode.ERR_FAIL) {
shareSubject.onError(new RuntimeException(baseResponse.errMsg));
} else if (baseResponse.errCode == WBConstants.ErrorCode.ERR_CANCEL) {
shareSubject.onCompleted();
} else {
shareSubject.onNext(baseResponse);
shareSubject.onCompleted();
}
}
// FIXME synchronized mWeiboShareAPI
public synchronized void onCreate(Activity activity, IWeiboHandler.Response response, Bundle savedInstanceState) {
mWeiboShareAPI = WeiboShareSDK.createWeiboAPI(activity, authInfo.getAppKey()); // appId
mWeiboShareAPI.registerApp();
if (savedInstanceState != null) {
mWeiboShareAPI.handleWeiboResponse(activity.getIntent(), response);
}
}
}