├── .gitignore ├── src ├── test │ ├── resources │ │ └── json │ │ │ ├── account.json │ │ │ ├── userTrends.json │ │ │ ├── cursoredFavoriteTags.json │ │ │ ├── trends.json │ │ │ ├── rateLimitStatus.json │ │ │ ├── profile.json │ │ │ ├── status.json │ │ │ ├── favorite.json │ │ │ ├── comment.json │ │ │ ├── repost.json │ │ │ ├── users.json │ │ │ ├── cursoredUsers.json │ │ │ ├── timeline.json │ │ │ ├── cursoredFavorites.json │ │ │ ├── comments.json │ │ │ ├── cursoredComments.json │ │ │ ├── hotStatuses.json │ │ │ └── repostTimeline.json │ └── java │ │ └── org │ │ └── springframework │ │ └── social │ │ ├── weibo │ │ ├── util │ │ │ └── StringUtilsTest.java │ │ ├── api │ │ │ └── impl │ │ │ │ ├── WeiboProfileMatcher.java │ │ │ │ ├── AbstractWeiboOperationsTest.java │ │ │ │ ├── UserTemplateTest.java │ │ │ │ ├── AccountTemplateTest.java │ │ │ │ ├── FavoriteTemplateTest.java │ │ │ │ └── FriendTemplateTest.java │ │ └── matcher │ │ │ └── StatusMatcher.java │ │ └── test │ │ └── client │ │ └── ResponseCreators.java └── main │ └── java │ └── org │ └── springframework │ └── social │ └── weibo │ ├── api │ ├── AuthorFilterType.java │ ├── StatusContentType.java │ ├── SourceFilterType.java │ ├── LimitTimeUnit.java │ ├── AccountOperations.java │ ├── UserOperations.java │ ├── impl │ │ ├── json │ │ │ ├── TimelineDateDeserializer.java │ │ │ ├── FollowedTrendMixin.java │ │ │ ├── UserTrendMixin.java │ │ │ ├── ApiRateLimitMixin.java │ │ │ ├── DateInSecondsDeserializer.java │ │ │ ├── TrendsWrapperMixin.java │ │ │ ├── FavoriteMixin.java │ │ │ ├── CommentMixin.java │ │ │ ├── DateFormatDeserializer.java │ │ │ ├── RateLimitStatusMixin.java │ │ │ ├── StatusMixin.java │ │ │ ├── WeiboModule.java │ │ │ ├── WeiboProfileMixin.java │ │ │ └── TrendsDeserializer.java │ │ ├── AccountTemplate.java │ │ ├── UserTemplate.java │ │ ├── AbstractWeiboOperations.java │ │ ├── WeiboErrorHandler.java │ │ ├── TrendTemplate.java │ │ ├── FavoriteTemplate.java │ │ ├── FriendTemplate.java │ │ └── WeiboTemplate.java │ ├── Weibo.java │ ├── TrendOperations.java │ ├── FollowedTrend.java │ ├── TrendsWrapper.java │ ├── FavoriteOperations.java │ ├── CursoredList.java │ ├── UserTrend.java │ ├── FriendOperations.java │ ├── ApiRateLimit.java │ ├── Favorite.java │ ├── Trends.java │ ├── Comment.java │ ├── CommentOperations.java │ ├── RateLimitStatus.java │ ├── Status.java │ └── TimelineOperations.java │ ├── util │ └── StringUtils.java │ └── connect │ ├── WeiboServiceProvider.java │ ├── WeiboConnectionFactory.java │ ├── WeiboAdapter.java │ └── WeiboOAuth2Template.java ├── README.md └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.classpath 3 | /.project 4 | /.settings 5 | -------------------------------------------------------------------------------- /src/test/resources/json/account.json: -------------------------------------------------------------------------------- 1 | { 2 | "uid": "123" 3 | } -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/AuthorFilterType.java: -------------------------------------------------------------------------------- 1 | package org.springframework.social.weibo.api; 2 | 3 | public enum AuthorFilterType { 4 | 5 | ALL, FRIENDS, OTHERS 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/StatusContentType.java: -------------------------------------------------------------------------------- 1 | package org.springframework.social.weibo.api; 2 | 3 | public enum StatusContentType { 4 | ALL, ORIGINAL, IMAGE, VIDEO, MUSIC 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/SourceFilterType.java: -------------------------------------------------------------------------------- 1 | package org.springframework.social.weibo.api; 2 | 3 | public enum SourceFilterType { 4 | 5 | ALL, FROM_WEIBO, FROM_OTHERS 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/test/resources/json/userTrends.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "num" : 225673, 3 | "hotword" : "苹果", 4 | "trend_id" : 1567898 5 | }, { 6 | "num" : 225674, 7 | "hotword" : "Apple", 8 | "trend_id" : 1567899 9 | } 10 | ] 11 | -------------------------------------------------------------------------------- /src/test/resources/json/cursoredFavoriteTags.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags" : [{ 3 | "id" : 23, 4 | "tag" : "80后", 5 | "count" : 25369 6 | }, { 7 | "id" : 24, 8 | "tag" : "80后" 9 | } 10 | ], 11 | "total_number" : 6 12 | } 13 | -------------------------------------------------------------------------------- /src/test/resources/json/trends.json: -------------------------------------------------------------------------------- 1 | { 2 | "trends": { 3 | "2011-05-31 11:46": [ 4 | { 5 | "name": "测试", 6 | "query": "测试", 7 | "amount": 1500, 8 | "delta": 100 9 | } 10 | ], 11 | "2011-05-31 10:46": [ 12 | { 13 | "name": "苹果", 14 | "query": "苹果", 15 | "amount": 123, 16 | "delta": 0 17 | }, 18 | { 19 | "name": "百度", 20 | "query": "百度", 21 | "amount": 156, 22 | "delta": 10 23 | } 24 | ] 25 | }, 26 | "as_of": 1280833537 27 | } -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/LimitTimeUnit.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api; 17 | 18 | public enum LimitTimeUnit { 19 | HOURS, DAYS 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/AccountOperations.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api; 17 | 18 | public interface AccountOperations { 19 | 20 | long getUid(); 21 | 22 | RateLimitStatus getRateLimitStatus(); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/test/resources/json/rateLimitStatus.json: -------------------------------------------------------------------------------- 1 | { 2 | "api_rate_limits" : [{ 3 | "api" : "/statuses/update", 4 | "limit" : 30, 5 | "limit_time_unit" : "HOURS", 6 | "remaining_hits" : 10 7 | }, { 8 | "api" : "/comments/create", 9 | "limit" : 60, 10 | "limit_time_unit" : "HOURS", 11 | "remaining_hits" : 60 12 | }, { 13 | "api" : "/direct_messages/new", 14 | "limit" : 60, 15 | "limit_time_unit" : "HOURS", 16 | "remaining_hits" : 60 17 | }, { 18 | "api" : "/friendships/create", 19 | "limit" : 60, 20 | "limit_time_unit" : "HOURS", 21 | "remaining_hits" : 60 22 | }, { 23 | "api" : "/friendships/create", 24 | "limit" : 100, 25 | "limit_time_unit" : "DAYS", 26 | "remaining_hits" : 100 27 | } 28 | ], 29 | "ip_limit" : 1000, 30 | "limit_time_unit" : "HOURS", 31 | "remaining_ip_hits" : 999, 32 | "remaining_user_hits" : 149, 33 | "reset_time" : "2012-02-29 18:00:00", 34 | "reset_time_in_seconds" : 2295, 35 | "user_limit" : 150 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/UserOperations.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api; 17 | 18 | public interface UserOperations { 19 | 20 | WeiboProfile getUserProfileById(long Uid); 21 | 22 | WeiboProfile getUserProfileByScreenName(String screenName); 23 | 24 | WeiboProfile getUserProfileByDomainName(String domainName); 25 | 26 | } -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/impl/json/TimelineDateDeserializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api.impl.json; 17 | 18 | class TimelineDateDeserializer extends DateFormatDeserializer { 19 | 20 | private static final String TIMELINE_DATE_FORMAT = "EEE MMM dd HH:mm:ss ZZZZZ yyyy"; 21 | 22 | @Override 23 | protected String getDateFormat() { 24 | return TIMELINE_DATE_FORMAT; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/impl/json/FollowedTrendMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api.impl.json; 17 | 18 | import org.codehaus.jackson.annotate.JsonProperty; 19 | 20 | /** 21 | * Annotated mixin to add Jackson annotations to FollowedTrend. 22 | * 23 | * @author edva8332 24 | */ 25 | abstract class FollowedTrendMixin { 26 | 27 | @JsonProperty("trend_id") 28 | long trendId; 29 | @JsonProperty("is_follow") 30 | boolean followed; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/Weibo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api; 17 | 18 | public interface Weibo { 19 | 20 | AccountOperations accountOperations(); 21 | 22 | CommentOperations commentOperations(); 23 | 24 | FriendOperations friendOperations(); 25 | 26 | FavoriteOperations favoriteOperations(); 27 | 28 | TimelineOperations timelineOperations(); 29 | 30 | UserOperations userOperations(); 31 | 32 | TrendOperations trendOperations(); 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/impl/json/UserTrendMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api.impl.json; 17 | 18 | import org.codehaus.jackson.annotate.JsonIgnoreProperties; 19 | import org.codehaus.jackson.annotate.JsonProperty; 20 | 21 | /** 22 | * Annotated mixin to add Jackson annotations to UserTrend. 23 | * 24 | * @author edva8332 25 | */ 26 | @JsonIgnoreProperties(ignoreUnknown = true) 27 | abstract class UserTrendMixin { 28 | 29 | String num; 30 | String hotword; 31 | @JsonProperty("trend_id") 32 | long id; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/impl/json/ApiRateLimitMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api.impl.json; 17 | 18 | import org.codehaus.jackson.annotate.JsonIgnoreProperties; 19 | import org.codehaus.jackson.annotate.JsonProperty; 20 | import org.springframework.social.weibo.api.LimitTimeUnit; 21 | 22 | @JsonIgnoreProperties(ignoreUnknown = true) 23 | class ApiRateLimitMixin { 24 | 25 | String api; 26 | int limit; 27 | @JsonProperty("limit_time_unit") 28 | LimitTimeUnit limitTimeUnit; 29 | @JsonProperty("remaining_hits") 30 | int remainingHits; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # spring-social-weibo 2 | 3 | This project is an [Spring Social](http://www.springsource.org/spring-social) extension for [weibo](http://www.weibo.com). 4 | Currently, only basic operations are supported: 5 | 6 | * get profile by id or username 7 | * get statuses: user timeline, home timeline, public timeline 8 | * update status 9 | * get friends 10 | * get followers 11 | 12 | If you are using this project, you should probably consider to read the [term of use of Weibo](http://open.weibo.com/wiki/%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91%E8%80%85%E5%8D%8F%E8%AE%AE) 13 | 14 | # Getting started 15 | 16 | In order to get started with spring-social-weibo, you have to follow only 3 simple steps. 17 | 18 | ## Step 1: Get the code from GitHub 19 | 20 | git clone --recursive git://github.com/vergnes/spring-social-weibo.git 21 | 22 | ## Step 2: Use Maven to build project 23 | 24 | cd spring-social-weibo 25 | mvn clean install 26 | 27 | ## Step 3: Add spring-social-weibo as a Maven dependency 28 | 29 | 30 | org.springframework.social 31 | spring-social-weibo 32 | 1.0.1-SNAPSHOT 33 | 34 | 35 | # License 36 | 37 | This project is available under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html). 38 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/social/weibo/util/StringUtilsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.util; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | 20 | import java.util.Arrays; 21 | import java.util.Collections; 22 | 23 | import org.junit.Test; 24 | 25 | public class StringUtilsTest { 26 | 27 | @Test 28 | public void testJoinEmpty() { 29 | assertEquals("", StringUtils.join(Collections.emptyList())); 30 | } 31 | 32 | @Test 33 | public void testJoinArray() { 34 | assertEquals("1,2,3", StringUtils.join(Arrays.asList(1L, 2L, 3L))); 35 | } 36 | 37 | @Test 38 | public void testBooleanToString() { 39 | assertEquals("1", StringUtils.booleanToString(true)); 40 | assertEquals("0", StringUtils.booleanToString(false)); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/impl/json/DateInSecondsDeserializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api.impl.json; 17 | 18 | import java.io.IOException; 19 | import java.util.Date; 20 | 21 | import org.codehaus.jackson.JsonParser; 22 | import org.codehaus.jackson.JsonProcessingException; 23 | import org.codehaus.jackson.map.DeserializationContext; 24 | import org.codehaus.jackson.map.JsonDeserializer; 25 | 26 | public class DateInSecondsDeserializer extends JsonDeserializer { 27 | 28 | private static final int MILLISECONDS = 1000; 29 | 30 | @Override 31 | public Date deserialize(JsonParser jp, DeserializationContext ctxt) 32 | throws IOException, JsonProcessingException { 33 | return new Date(jp.getLongValue() * MILLISECONDS); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/util/StringUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.util; 17 | 18 | import java.util.Iterator; 19 | 20 | public abstract class StringUtils { 21 | 22 | private StringUtils() { 23 | } 24 | 25 | public static String join(Iterable iterable) { 26 | Iterator iterator = iterable.iterator(); 27 | if (!iterator.hasNext()) { 28 | return ""; 29 | } 30 | StringBuilder stringBuilder = new StringBuilder(iterator.next() 31 | .toString()); 32 | while (iterator.hasNext()) { 33 | stringBuilder.append(","); 34 | stringBuilder.append(iterator.next().toString()); 35 | } 36 | return stringBuilder.toString(); 37 | } 38 | 39 | public static String booleanToString(boolean value) { 40 | return value ? "1" : "0"; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/TrendOperations.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api; 17 | 18 | import java.util.List; 19 | 20 | public interface TrendOperations { 21 | 22 | long follow(String trendName); 23 | 24 | TrendsWrapper getDailyTrends(); 25 | 26 | TrendsWrapper getDailyTrends(boolean onlyApplicationData); 27 | 28 | List getTrends(long userId); 29 | 30 | List getTrends(long userId, int pageSize, int pageNumber); 31 | 32 | FollowedTrend isFollowed(String trendName); 33 | 34 | TrendsWrapper getHourlyTrends(); 35 | 36 | TrendsWrapper getHourlyTrends(boolean onlyApplicationData); 37 | 38 | TrendsWrapper getWeeklyTrends(); 39 | 40 | TrendsWrapper getWeeklyTrends(boolean onlyApplicationData); 41 | 42 | boolean unfollow(long trendId); 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/FollowedTrend.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api; 17 | 18 | public class FollowedTrend { 19 | 20 | private long trendId; 21 | private boolean followed; 22 | 23 | /** 24 | * @return the trendId 25 | */ 26 | public long getTrendId() { 27 | return trendId; 28 | } 29 | 30 | /** 31 | * @param trendId 32 | * the trendId to set 33 | */ 34 | public void setTrendId(long trendId) { 35 | this.trendId = trendId; 36 | } 37 | 38 | /** 39 | * @return the followed 40 | */ 41 | public boolean isFollowed() { 42 | return followed; 43 | } 44 | 45 | /** 46 | * @param followed 47 | * the followed to set 48 | */ 49 | public void setFollowed(boolean followed) { 50 | this.followed = followed; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/test/resources/json/profile.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : 123, 3 | "idstr" : "123", 4 | "screen_name" : "Cirrus_Test1", 5 | "name" : "Cirrus_Test1", 6 | "province" : "11", 7 | "city" : "1000", 8 | "location" : "北京三元桥", 9 | "description" : "description", 10 | "url" : "http://myFavouriteUrl.com", 11 | "profile_image_url" : "http://tp4.sinaimg.cn/2429576463/50/0/1", 12 | "domain" : "domain", 13 | "gender" : "m", 14 | "followers_count" : 1, 15 | "friends_count" : 28, 16 | "statuses_count" : 1, 17 | "favourites_count" : 0, 18 | "created_at" : "Thu Oct 27 00:00:00 +0800 2011", 19 | "following" : false, 20 | "allow_all_act_msg" : false, 21 | "geo_enabled" : true, 22 | "verified" : false, 23 | "verified_type" : -1, 24 | "status" : { 25 | "created_at" : "Thu Oct 27 10:18:29 +0800 2011", 26 | "id" : 3373052761021575, 27 | "mid" : "3373052761021575", 28 | "idstr" : "3373052761021575", 29 | "text" : "Youpie", 30 | "source" : "????????????", 31 | "favorited" : true, 32 | "truncated" : true, 33 | "in_reply_to_status_id" : "", 34 | "in_reply_to_user_id" : "", 35 | "in_reply_to_screen_name" : "", 36 | "geo" : null, 37 | "reposts_count" : 0, 38 | "comments_count" : 0, 39 | "mlevel" : 0 40 | }, 41 | "allow_all_comment" : true, 42 | "avatar_large" : "http://tp4.sinaimg.cn/2429576463/180/0/1", 43 | "verified_reason" : "verified reason", 44 | "follow_me" : false, 45 | "online_status" : 0, 46 | "bi_followers_count" : 1, 47 | "lang" : "zh-cn" 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/connect/WeiboServiceProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.connect; 17 | 18 | import org.springframework.social.oauth2.AbstractOAuth2ServiceProvider; 19 | import org.springframework.social.weibo.api.Weibo; 20 | import org.springframework.social.weibo.api.impl.WeiboTemplate; 21 | 22 | /** 23 | * Twitter ServiceProvider implementation that exposes the Twitter 4j API 24 | * binding. 25 | * 26 | * @author Craig Walls 27 | */ 28 | public final class WeiboServiceProvider extends 29 | AbstractOAuth2ServiceProvider { 30 | 31 | public WeiboServiceProvider(String consumerKey, String consumerSecret) { 32 | super(new WeiboOAuth2Template(consumerKey, consumerSecret)); 33 | } 34 | 35 | @Override 36 | public Weibo getApi(String accessToken) { 37 | return new WeiboTemplate(accessToken); 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/TrendsWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api; 17 | 18 | import java.util.Date; 19 | import java.util.SortedSet; 20 | 21 | public class TrendsWrapper { 22 | 23 | private SortedSet trends; 24 | 25 | private Date asOf; 26 | 27 | /** 28 | * @return the trends 29 | */ 30 | public SortedSet getTrends() { 31 | return trends; 32 | } 33 | 34 | /** 35 | * @param trends 36 | * the trends to set 37 | */ 38 | public void setTrends(SortedSet trends) { 39 | this.trends = trends; 40 | } 41 | 42 | /** 43 | * @return the asOf 44 | */ 45 | public Date getAsOf() { 46 | return asOf; 47 | } 48 | 49 | /** 50 | * @param asOf 51 | * the asOf to set 52 | */ 53 | public void setAsOf(Date asOf) { 54 | this.asOf = asOf; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/impl/json/TrendsWrapperMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api.impl.json; 17 | 18 | import java.util.Date; 19 | import java.util.SortedSet; 20 | 21 | import org.codehaus.jackson.annotate.JsonIgnoreProperties; 22 | import org.codehaus.jackson.annotate.JsonProperty; 23 | import org.codehaus.jackson.map.annotate.JsonDeserialize; 24 | import org.springframework.social.weibo.api.Trends.Trend; 25 | 26 | /** 27 | * Annotated mixin to add Jackson annotations to TrendsWrapper. 28 | * 29 | * @author edva8332 30 | */ 31 | @JsonIgnoreProperties(ignoreUnknown = true) 32 | abstract class TrendsWrapperMixin { 33 | 34 | @JsonProperty("trends") 35 | @JsonDeserialize(using = TrendsDeserializer.class) 36 | SortedSet trends; 37 | 38 | @JsonProperty("as_of") 39 | @JsonDeserialize(using = DateInSecondsDeserializer.class) 40 | Date asOf; 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/FavoriteOperations.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api; 17 | 18 | import java.util.List; 19 | 20 | import org.springframework.social.weibo.api.Favorite.Tag; 21 | 22 | public interface FavoriteOperations { 23 | 24 | Favorite createFavorite(long id); 25 | 26 | Favorite deleteFavorite(long id); 27 | 28 | boolean deleteFavorites(List ids); 29 | 30 | Favorite getFavorite(long id); 31 | 32 | CursoredList getFavorites(); 33 | 34 | CursoredList getFavorites(int pageSize, int pageNumber); 35 | 36 | CursoredList getFavoritesByTag(long tagId); 37 | 38 | CursoredList getFavoritesByTag(long tagId, int pageSize, 39 | int pageNumber); 40 | 41 | CursoredList getTags(); 42 | 43 | CursoredList getTags(int pageSize, int pageNumber); 44 | 45 | Favorite updateTags(long id, List tags); 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/CursoredList.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api; 17 | 18 | import java.util.LinkedList; 19 | 20 | public class CursoredList extends LinkedList { 21 | 22 | private static final long serialVersionUID = 1L; 23 | 24 | private long previousCursor; 25 | private long nextCursor; 26 | private int totalNumber; 27 | 28 | public long getPreviousCursor() { 29 | return previousCursor; 30 | } 31 | 32 | public void setPreviousCursor(long previousCursor) { 33 | this.previousCursor = previousCursor; 34 | } 35 | 36 | public long getNextCursor() { 37 | return nextCursor; 38 | } 39 | 40 | public void setNextCursor(long nextCursor) { 41 | this.nextCursor = nextCursor; 42 | } 43 | 44 | public int getTotalNumber() { 45 | return totalNumber; 46 | } 47 | 48 | public void setTotalNumber(int totalNumber) { 49 | this.totalNumber = totalNumber; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/impl/json/FavoriteMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api.impl.json; 17 | 18 | import java.util.Date; 19 | 20 | import org.codehaus.jackson.annotate.JsonIgnoreProperties; 21 | import org.codehaus.jackson.annotate.JsonProperty; 22 | import org.codehaus.jackson.map.annotate.JsonDeserialize; 23 | import org.springframework.social.weibo.api.Status; 24 | 25 | /** 26 | * Annotated mixin to add Jackson annotations to Favorite. 27 | * 28 | * @author edva8332 29 | */ 30 | @JsonIgnoreProperties(ignoreUnknown = true) 31 | abstract class FavoriteMixin { 32 | 33 | @JsonIgnoreProperties(ignoreUnknown = true) 34 | abstract static class TagMixin { 35 | long id; 36 | @JsonProperty("tag") 37 | String value; 38 | int count; 39 | } 40 | 41 | @JsonProperty("status") 42 | Status status; 43 | @JsonProperty("favorited_time") 44 | @JsonDeserialize(using = TimelineDateDeserializer.class) 45 | Date favoritedTime; 46 | } 47 | -------------------------------------------------------------------------------- /src/test/resources/json/status.json: -------------------------------------------------------------------------------- 1 | { 2 | "created_at": "Tue May 31 17:46:55 +0800 2011", 3 | "id": 11488058246, 4 | "text": "你好", 5 | "source": "Sina microblogging", 6 | "favorited": false, 7 | "truncated": false, 8 | "in_reply_to_status_id": "", 9 | "in_reply_to_user_id": "", 10 | "in_reply_to_screen_name": "", 11 | "mid": "5612814510546515491", 12 | "reposts_count": 8, 13 | "comments_count": 9, 14 | "geo" : null, 15 | "user": { 16 | "id": 1404376560, 17 | "screen_name": "zaku", 18 | "name": "zaku", 19 | "province": "11", 20 | "city": "5", 21 | "location": "Beijing's Chaoyang District.", 22 | "description": "Fifty years of life, is dreamlike; give birth, then you have died, warrior What else regret.", 23 | "url": "http://blog.sina.com.cn/zaku", 24 | "profile_image_url": "http://tp1.sinaimg.cn/1404376560/50/0/1", 25 | "domain": "zaku", 26 | "gender": "m", 27 | "followers_count": 1204, 28 | "friends_count": 447, 29 | "statuses_count": 2908, 30 | "favourites_count": 0, 31 | "created_at": "Fri Aug 28 00:00:00 +0800 2009", 32 | "following": false, 33 | "allow_all_act_msg": false, 34 | "remark": "", 35 | "geo_enabled": true, 36 | "verified": false, 37 | "allow_all_comment": true, 38 | "avatar_large": "http://tp1.sinaimg.cn/1404376560/180/0/1", 39 | "verified_reason": "", 40 | "follow_me": false, 41 | "online_status": 0, 42 | "bi_followers_count": 215 43 | } 44 | } -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/UserTrend.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api; 17 | 18 | public class UserTrend { 19 | 20 | private long id; 21 | private String num; 22 | private String hotword; 23 | 24 | /** 25 | * @return the id 26 | */ 27 | public long getId() { 28 | return id; 29 | } 30 | 31 | /** 32 | * @param id 33 | * the id to set 34 | */ 35 | public void setId(long id) { 36 | this.id = id; 37 | } 38 | 39 | /** 40 | * @return the num 41 | */ 42 | public String getNum() { 43 | return num; 44 | } 45 | 46 | /** 47 | * @param num 48 | * the num to set 49 | */ 50 | public void setNum(String num) { 51 | this.num = num; 52 | } 53 | 54 | /** 55 | * @return the hotword 56 | */ 57 | public String getHotword() { 58 | return hotword; 59 | } 60 | 61 | /** 62 | * @param hotword 63 | * the hotword to set 64 | */ 65 | public void setHotword(String hotword) { 66 | this.hotword = hotword; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/test/resources/json/favorite.json: -------------------------------------------------------------------------------- 1 | { 2 | "status" : { 3 | "created_at" : "Tue May 31 17:46:55 +0800 2011", 4 | "id" : 11488058246, 5 | "text" : "求关注。", 6 | "source" : "新浪微博", 7 | "favorited" : false, 8 | "truncated" : false, 9 | "in_reply_to_status_id" : "", 10 | "in_reply_to_user_id" : "", 11 | "in_reply_to_screen_name" : "", 12 | "geo" : null, 13 | "mid" : "5612814510546515491", 14 | "reposts_count" : 8, 15 | "comments_count" : 9, 16 | "annotations" : [], 17 | "user" : { 18 | "id" : 1404376560, 19 | "screen_name" : "zaku", 20 | "name" : "zaku", 21 | "province" : "11", 22 | "city" : "5", 23 | "location" : "北京 朝阳区", 24 | "description" : "人生五十年,乃如梦如幻;有生斯有死,壮士复何憾。", 25 | "url" : "http://blog.sina.com.cn/zaku", 26 | "profile_image_url" : "http://tp1.sinaimg.cn/1404376560/50/0/1", 27 | "domain" : "zaku", 28 | "gender" : "m", 29 | "followers_count" : 1204, 30 | "friends_count" : 447, 31 | "statuses_count" : 2908, 32 | "favourites_count" : 0, 33 | "created_at" : "Fri Aug 28 00:00:00 +0800 2009", 34 | "following" : false, 35 | "allow_all_act_msg" : false, 36 | "remark" : "", 37 | "geo_enabled" : true, 38 | "verified" : false, 39 | "allow_all_comment" : true, 40 | "avatar_large" : "http://tp1.sinaimg.cn/1404376560/180/0/1", 41 | "verified_reason" : "", 42 | "follow_me" : false, 43 | "online_status" : 0, 44 | "bi_followers_count" : 215 45 | } 46 | }, 47 | "tags" : [{ 48 | "id" : 23, 49 | "tag" : "80后", 50 | "count" : 25369 51 | }, { 52 | "id" : 24, 53 | "tag" : "80后" 54 | } 55 | ], 56 | "favorited_time" : "Thu Jun 02 15:16:16 +0800 2011" 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/impl/json/CommentMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api.impl.json; 17 | 18 | import java.util.Date; 19 | 20 | import org.codehaus.jackson.annotate.JsonIgnoreProperties; 21 | import org.codehaus.jackson.annotate.JsonProperty; 22 | import org.codehaus.jackson.map.annotate.JsonDeserialize; 23 | import org.springframework.social.weibo.api.Status; 24 | import org.springframework.social.weibo.api.WeiboProfile; 25 | 26 | /** 27 | * Annotated mixin to add Jackson annotations to Comment. 28 | * 29 | * @author edva8332 30 | */ 31 | @JsonIgnoreProperties(ignoreUnknown = true) 32 | abstract class CommentMixin { 33 | CommentMixin( 34 | @JsonProperty("id") long id, 35 | @JsonProperty("created_at") @JsonDeserialize(using = TimelineDateDeserializer.class) Date createAt, 36 | @JsonProperty("text") String text, 37 | @JsonProperty("source") String source) { 38 | } 39 | 40 | @JsonProperty("mid") 41 | String mid; 42 | @JsonProperty("user") 43 | WeiboProfile user; 44 | @JsonProperty("status") 45 | Status status; 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/impl/json/DateFormatDeserializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api.impl.json; 17 | 18 | import java.io.IOException; 19 | import java.text.ParseException; 20 | import java.text.SimpleDateFormat; 21 | import java.util.Date; 22 | import java.util.Locale; 23 | 24 | import org.codehaus.jackson.JsonParser; 25 | import org.codehaus.jackson.JsonProcessingException; 26 | import org.codehaus.jackson.map.DeserializationContext; 27 | import org.codehaus.jackson.map.JsonDeserializer; 28 | 29 | public class DateFormatDeserializer extends JsonDeserializer { 30 | 31 | public DateFormatDeserializer() { 32 | super(); 33 | } 34 | 35 | @Override 36 | public Date deserialize(JsonParser jp, DeserializationContext ctxt) 37 | throws IOException, JsonProcessingException { 38 | try { 39 | return new SimpleDateFormat(getDateFormat(), Locale.ENGLISH) 40 | .parse(jp.getText()); 41 | } catch (ParseException e) { 42 | return null; 43 | } 44 | } 45 | 46 | protected String getDateFormat() { 47 | return "yyyy-MM-dd HH:mm:ss"; 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/FriendOperations.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api; 17 | 18 | import java.util.List; 19 | 20 | public interface FriendOperations { 21 | 22 | WeiboProfile createFriend(long uid); 23 | 24 | WeiboProfile deleteFriend(long uid); 25 | 26 | List getActiveFollowers(long uid); 27 | 28 | List getActiveFollowers(long uid, int pageSize); 29 | 30 | CursoredList getBilateralFriends(long uid); 31 | 32 | CursoredList getBilateralFriends(long uid, int pageSize, 33 | int pageNumber); 34 | 35 | CursoredList getCommonFriends(long user1Uid, long user2Uid); 36 | 37 | CursoredList getCommonFriends(long user1Uid, long user2Uid, 38 | int pageSize, int pageNumber); 39 | 40 | CursoredList getFollowers(long uid); 41 | 42 | CursoredList getFollowers(long uid, int pageSize, 43 | int pageNumber); 44 | 45 | CursoredList getFriends(long uid); 46 | 47 | CursoredList getFriends(long uid, int pageSize, int pageNumber); 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/impl/AccountTemplate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api.impl; 17 | 18 | import java.util.Map; 19 | 20 | import org.codehaus.jackson.map.ObjectMapper; 21 | import org.springframework.social.weibo.api.AccountOperations; 22 | import org.springframework.social.weibo.api.RateLimitStatus; 23 | import org.springframework.web.client.RestTemplate; 24 | 25 | class AccountTemplate extends AbstractWeiboOperations implements 26 | AccountOperations { 27 | 28 | protected AccountTemplate(ObjectMapper objectMapper, 29 | RestTemplate restTemplate, boolean isAuthorized) { 30 | super(objectMapper, restTemplate, isAuthorized); 31 | } 32 | 33 | @Override 34 | public long getUid() { 35 | requireAuthorization(); 36 | return Long.valueOf(restTemplate 37 | .getForObject(buildUri("account/get_uid.json"), Map.class) 38 | .get("uid").toString()); 39 | } 40 | 41 | @Override 42 | public RateLimitStatus getRateLimitStatus() { 43 | requireAuthorization(); 44 | return restTemplate.getForObject( 45 | buildUri("account/rate_limit_status.json"), 46 | RateLimitStatus.class); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/connect/WeiboConnectionFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.connect; 17 | 18 | import java.util.Date; 19 | 20 | import org.springframework.social.connect.Connection; 21 | import org.springframework.social.connect.ConnectionData; 22 | import org.springframework.social.connect.support.OAuth2ConnectionFactory; 23 | import org.springframework.social.weibo.api.Weibo; 24 | 25 | /** 26 | * WeiboConnectionFactory that creates connections that expose the Weibo API 27 | * binding. 28 | * 29 | * @author edva8332 30 | */ 31 | public class WeiboConnectionFactory extends OAuth2ConnectionFactory { 32 | 33 | public WeiboConnectionFactory(String consumerKey, String consumerSecret) { 34 | super("weibo", new WeiboServiceProvider(consumerKey, consumerSecret), 35 | new WeiboAdapter()); 36 | } 37 | 38 | @Override 39 | public Connection createConnection(ConnectionData data) { 40 | Connection result = null; 41 | if (data.getExpireTime() == null 42 | || new Date(data.getExpireTime()).after(new Date())) { 43 | result = super.createConnection(data); 44 | } 45 | return result; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/impl/json/RateLimitStatusMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api.impl.json; 17 | 18 | import java.util.Date; 19 | import java.util.List; 20 | 21 | import org.codehaus.jackson.annotate.JsonIgnoreProperties; 22 | import org.codehaus.jackson.annotate.JsonProperty; 23 | import org.codehaus.jackson.map.annotate.JsonDeserialize; 24 | import org.springframework.social.weibo.api.ApiRateLimit; 25 | import org.springframework.social.weibo.api.LimitTimeUnit; 26 | 27 | @JsonIgnoreProperties(ignoreUnknown = true) 28 | class RateLimitStatusMixin { 29 | 30 | @JsonProperty("ip_limit") 31 | int ipLimit; 32 | @JsonProperty("limit_time_unit") 33 | LimitTimeUnit limitTimeUnit; 34 | @JsonProperty("remaining_ip_hits") 35 | int remainingIpHits; 36 | @JsonProperty("remaining_user_hits") 37 | int remainingUserHits; 38 | @JsonProperty("reset_time") 39 | @JsonDeserialize(using = DateFormatDeserializer.class) 40 | Date resetTime; 41 | @JsonProperty("reset_time_in_seconds") 42 | int resetTimeInSeconds; 43 | @JsonProperty("user_limit") 44 | int userLimit; 45 | @JsonProperty("api_rate_limits") 46 | List apiRateLimits; 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/social/weibo/api/impl/WeiboProfileMatcher.java: -------------------------------------------------------------------------------- 1 | package org.springframework.social.weibo.api.impl; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import org.springframework.social.weibo.api.Status; 7 | import org.springframework.social.weibo.api.WeiboProfile; 8 | 9 | public abstract class WeiboProfileMatcher { 10 | 11 | public static void verifyWeiboProfile(WeiboProfile profile) { 12 | assertEquals(123L, profile.getId().longValue()); 13 | assertEquals("Cirrus_Test1", profile.getScreenName()); 14 | assertEquals("http://tp4.sinaimg.cn/2429576463/180/0/1", 15 | profile.getAvatarLarge()); 16 | assertEquals(1, profile.getBiFollowersCount()); 17 | assertEquals(1000, profile.getCity()); 18 | assertEquals(1319644800000L, profile.getCreatedAt().getTime()); 19 | assertEquals("description", profile.getDescription()); 20 | assertEquals(0, profile.getFavouritesCount()); 21 | assertEquals(1, profile.getFollowersCount()); 22 | assertEquals(28, profile.getFriendsCount()); 23 | assertEquals("m", profile.getGender()); 24 | assertEquals("北京三元桥", profile.getLocation()); 25 | assertEquals("Cirrus_Test1", profile.getName()); 26 | assertEquals(0, profile.getOnlineStatus()); 27 | assertEquals("http://tp4.sinaimg.cn/2429576463/50/0/1", 28 | profile.getProfileImageUrl()); 29 | assertEquals(11, profile.getProvince()); 30 | assertEquals(1, profile.getStatusesCount()); 31 | assertEquals("http://myFavouriteUrl.com", profile.getUrl()); 32 | assertEquals("domain", profile.getDomain()); 33 | assertEquals("verified reason", profile.getVerifiedReason()); 34 | 35 | Status status = profile.getStatus(); 36 | assertEquals(1319681909000L, status.getCreatedAt().getTime()); 37 | assertEquals(3373052761021575L, status.getId().longValue()); 38 | assertEquals("3373052761021575", status.getMid()); 39 | assertEquals("Youpie", status.getText()); 40 | assertTrue(status.isFavorited()); 41 | assertTrue(status.isTruncated()); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/social/weibo/matcher/StatusMatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.matcher; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | import static org.junit.Assert.assertFalse; 20 | import static org.junit.Assert.assertNotNull; 21 | 22 | import org.springframework.social.weibo.api.CursoredList; 23 | import org.springframework.social.weibo.api.Status; 24 | import org.springframework.social.weibo.api.WeiboProfile; 25 | 26 | public abstract class StatusMatcher { 27 | 28 | public static void verifyStatus(Status status) { 29 | assertEquals(1306835215000L, status.getCreatedAt().getTime()); 30 | assertEquals(11488058246L, status.getId().longValue()); 31 | assertEquals("5612814510546515491", status.getMid()); 32 | assertFalse(status.isFavorited()); 33 | assertFalse(status.isTruncated()); 34 | WeiboProfile user = status.getUser(); 35 | assertNotNull(user); 36 | assertEquals("zaku", user.getScreenName()); 37 | assertEquals(1404376560L, user.getId().longValue()); 38 | } 39 | 40 | public static void verifyStatusList( 41 | CursoredList statuses) { 42 | assertEquals(2, statuses.size()); 43 | assertEquals(1L, statuses.getNextCursor()); 44 | assertEquals(0, statuses.getPreviousCursor()); 45 | assertEquals(81655, statuses.getTotalNumber()); 46 | } 47 | 48 | private StatusMatcher() { 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/impl/UserTemplate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api.impl; 17 | 18 | import org.codehaus.jackson.map.ObjectMapper; 19 | import org.springframework.social.weibo.api.UserOperations; 20 | import org.springframework.social.weibo.api.WeiboProfile; 21 | import org.springframework.web.client.RestTemplate; 22 | 23 | class UserTemplate extends AbstractWeiboOperations implements UserOperations { 24 | 25 | protected UserTemplate(ObjectMapper objectMapper, 26 | RestTemplate restTemplate, boolean isAuthorized) { 27 | super(objectMapper, restTemplate, isAuthorized); 28 | } 29 | 30 | @Override 31 | public WeiboProfile getUserProfileByDomainName(String domainName) { 32 | requireAuthorization(); 33 | return restTemplate.getForObject( 34 | buildUri("users/domain_show.json", "domain", domainName), 35 | WeiboProfile.class); 36 | } 37 | 38 | @Override 39 | public WeiboProfile getUserProfileById(long uid) { 40 | requireAuthorization(); 41 | return restTemplate.getForObject( 42 | buildUri("users/show.json", "uid", uid), WeiboProfile.class); 43 | } 44 | 45 | @Override 46 | public WeiboProfile getUserProfileByScreenName(String screenName) { 47 | requireAuthorization(); 48 | return restTemplate.getForObject( 49 | buildUri("users/show.json", "screen_name", screenName), 50 | WeiboProfile.class); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/ApiRateLimit.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api; 17 | 18 | public class ApiRateLimit { 19 | 20 | private String api; 21 | private int limit; 22 | private LimitTimeUnit limitTimeUnit; 23 | private int remainingHits; 24 | 25 | /** 26 | * @return the api 27 | */ 28 | public String getApi() { 29 | return api; 30 | } 31 | 32 | /** 33 | * @param api 34 | * the api to set 35 | */ 36 | public void setApi(String api) { 37 | this.api = api; 38 | } 39 | 40 | /** 41 | * @return the limit 42 | */ 43 | public int getLimit() { 44 | return limit; 45 | } 46 | 47 | /** 48 | * @param limit 49 | * the limit to set 50 | */ 51 | public void setLimit(int limit) { 52 | this.limit = limit; 53 | } 54 | 55 | /** 56 | * @return the limitTimeUnit 57 | */ 58 | public LimitTimeUnit getLimitTimeUnit() { 59 | return limitTimeUnit; 60 | } 61 | 62 | /** 63 | * @param limitTimeUnit 64 | * the limitTimeUnit to set 65 | */ 66 | public void setLimitTimeUnit(LimitTimeUnit limitTimeUnit) { 67 | this.limitTimeUnit = limitTimeUnit; 68 | } 69 | 70 | /** 71 | * @return the remainingHits 72 | */ 73 | public int getRemainingHits() { 74 | return remainingHits; 75 | } 76 | 77 | /** 78 | * @param remainingHits 79 | * the remainingHits to set 80 | */ 81 | public void setRemainingHits(int remainingHits) { 82 | this.remainingHits = remainingHits; 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/social/weibo/api/impl/AbstractWeiboOperationsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api.impl; 17 | 18 | import org.codehaus.jackson.map.ObjectMapper; 19 | import org.junit.Before; 20 | import org.springframework.core.io.ClassPathResource; 21 | import org.springframework.core.io.Resource; 22 | import org.springframework.http.HttpHeaders; 23 | import org.springframework.http.MediaType; 24 | import org.springframework.social.test.client.MockRestServiceServer; 25 | import org.springframework.web.client.RestTemplate; 26 | 27 | public abstract class AbstractWeiboOperationsTest { 28 | 29 | protected MockRestServiceServer mockServer; 30 | private WeiboTemplate weiboTemplate; 31 | protected HttpHeaders responseHeaders; 32 | 33 | public AbstractWeiboOperationsTest() { 34 | weiboTemplate = new WeiboTemplate("accessToken"); 35 | mockServer = MockRestServiceServer.createServer(weiboTemplate 36 | .getRestTemplate()); 37 | responseHeaders = new HttpHeaders(); 38 | responseHeaders.setContentType(MediaType.APPLICATION_JSON); 39 | } 40 | 41 | @Before 42 | public abstract void setUp(); 43 | 44 | protected RestTemplate getRestTemplate() { 45 | return weiboTemplate.getRestTemplate(); 46 | } 47 | 48 | protected ObjectMapper getObjectMapper() { 49 | return weiboTemplate.getObjectMapper(); 50 | } 51 | 52 | protected Resource jsonResource(String file) { 53 | ClassPathResource classPathResource = new ClassPathResource("json/" 54 | + file + ".json"); 55 | return classPathResource; 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/impl/json/StatusMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api.impl.json; 17 | 18 | import java.util.Date; 19 | 20 | import org.codehaus.jackson.annotate.JsonIgnoreProperties; 21 | import org.codehaus.jackson.annotate.JsonProperty; 22 | import org.codehaus.jackson.map.annotate.JsonDeserialize; 23 | import org.springframework.social.weibo.api.Status; 24 | import org.springframework.social.weibo.api.WeiboProfile; 25 | 26 | /** 27 | * Annotated mixin to add Jackson annotations to Status. 28 | * 29 | * @author edva8332 30 | */ 31 | @JsonIgnoreProperties(ignoreUnknown = true) 32 | abstract class StatusMixin { 33 | 34 | StatusMixin( 35 | @JsonProperty("id") Long id, 36 | @JsonProperty("created_at") @JsonDeserialize(using = TimelineDateDeserializer.class) Date createAt, 37 | @JsonProperty("text") String text, 38 | @JsonProperty("source") String source, 39 | @JsonProperty("favorited") boolean favorited, 40 | @JsonProperty("truncated") boolean truncated, 41 | @JsonProperty("reposts_count") int repostsCount, 42 | @JsonProperty("comments_count") int commentsCount) { 43 | } 44 | 45 | @JsonProperty("in_reply_to_status_id") 46 | String inReplyToStatusId; 47 | @JsonProperty("in_reply_to_user_id") 48 | String inReplyToUserId; 49 | @JsonProperty("in_reply_to_screen_name") 50 | String inReplyToScreenName; 51 | @JsonProperty("mid") 52 | String mid; 53 | @JsonProperty("user") 54 | WeiboProfile user; 55 | @JsonProperty("retweeted_status") 56 | Status originalStatus; 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/impl/json/WeiboModule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api.impl.json; 17 | 18 | import org.codehaus.jackson.Version; 19 | import org.codehaus.jackson.map.module.SimpleModule; 20 | import org.springframework.social.weibo.api.ApiRateLimit; 21 | import org.springframework.social.weibo.api.Comment; 22 | import org.springframework.social.weibo.api.Favorite; 23 | import org.springframework.social.weibo.api.Favorite.Tag; 24 | import org.springframework.social.weibo.api.FollowedTrend; 25 | import org.springframework.social.weibo.api.RateLimitStatus; 26 | import org.springframework.social.weibo.api.Status; 27 | import org.springframework.social.weibo.api.TrendsWrapper; 28 | import org.springframework.social.weibo.api.UserTrend; 29 | import org.springframework.social.weibo.api.WeiboProfile; 30 | import org.springframework.social.weibo.api.impl.json.FavoriteMixin.TagMixin; 31 | 32 | public class WeiboModule extends SimpleModule { 33 | 34 | public WeiboModule() { 35 | super("WeiboModule", new Version(1, 0, 0, null)); 36 | } 37 | 38 | @Override 39 | public void setupModule(SetupContext context) { 40 | context.setMixInAnnotations(WeiboProfile.class, WeiboProfileMixin.class); 41 | context.setMixInAnnotations(Status.class, StatusMixin.class); 42 | context.setMixInAnnotations(Comment.class, CommentMixin.class); 43 | context.setMixInAnnotations(ApiRateLimit.class, ApiRateLimitMixin.class); 44 | context.setMixInAnnotations(RateLimitStatus.class, 45 | RateLimitStatusMixin.class); 46 | context.setMixInAnnotations(Favorite.class, FavoriteMixin.class); 47 | context.setMixInAnnotations(Tag.class, TagMixin.class); 48 | context.setMixInAnnotations(UserTrend.class, UserTrendMixin.class); 49 | context.setMixInAnnotations(FollowedTrend.class, 50 | FollowedTrendMixin.class); 51 | context.setMixInAnnotations(TrendsWrapper.class, 52 | TrendsWrapperMixin.class); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/test/resources/json/comment.json: -------------------------------------------------------------------------------- 1 | { 2 | "created_at" : "Wed Jun 01 00:50:25 +0800 2011", 3 | "id" : 12438492184, 4 | "text" : "我喜欢你做的", 5 | "source" : "新浪微博", 6 | "mid" : "202110601896455629", 7 | "user" : { 8 | "id" : 1404376560, 9 | "screen_name" : "zaku", 10 | "name" : "zaku", 11 | "province" : "11", 12 | "city" : "5", 13 | "location" : "北京 朝阳区", 14 | "description" : "人生五十年,乃如梦如幻;有生斯有死,壮士复何憾。", 15 | "url" : "http://blog.sina.com.cn/zaku", 16 | "profile_image_url" : "http://tp1.sinaimg.cn/1404376560/50/0/1", 17 | "domain" : "zaku", 18 | "gender" : "m", 19 | "followers_count" : 1204, 20 | "friends_count" : 447, 21 | "statuses_count" : 2908, 22 | "favourites_count" : 0, 23 | "created_at" : "Fri Aug 28 00:00:00 +0800 2009", 24 | "following" : false, 25 | "allow_all_act_msg" : false, 26 | "remark" : "", 27 | "geo_enabled" : true, 28 | "verified" : false, 29 | "allow_all_comment" : true, 30 | "avatar_large" : "http://tp1.sinaimg.cn/1404376560/180/0/1", 31 | "verified_reason" : "", 32 | "follow_me" : false, 33 | "online_status" : 0, 34 | "bi_followers_count" : 215 35 | }, 36 | "status" : { 37 | "created_at" : "Tue May 31 17:46:55 +0800 2011", 38 | "id" : 11488058246, 39 | "text" : "求关注。", 40 | "source" : "新浪微博", 41 | "favorited" : false, 42 | "truncated" : false, 43 | "in_reply_to_status_id" : "", 44 | "in_reply_to_user_id" : "", 45 | "in_reply_to_screen_name" : "", 46 | "geo" : null, 47 | "mid" : "5612814510546515491", 48 | "reposts_count" : 8, 49 | "comments_count" : 9, 50 | "annotations" : [], 51 | "user" : { 52 | "id" : 1404376560, 53 | "screen_name" : "zaku", 54 | "name" : "zaku", 55 | "province" : "11", 56 | "city" : "5", 57 | "location" : "北京 朝阳区", 58 | "description" : "人生五十年,乃如梦如幻;有生斯有死,壮士复何憾。", 59 | "url" : "http://blog.sina.com.cn/zaku", 60 | "profile_image_url" : "http://tp1.sinaimg.cn/1404376560/50/0/1", 61 | "domain" : "zaku", 62 | "gender" : "m", 63 | "followers_count" : 1204, 64 | "friends_count" : 447, 65 | "statuses_count" : 2908, 66 | "favourites_count" : 0, 67 | "created_at" : "Fri Aug 28 00:00:00 +0800 2009", 68 | "following" : false, 69 | "allow_all_act_msg" : false, 70 | "remark" : "", 71 | "geo_enabled" : true, 72 | "verified" : false, 73 | "allow_all_comment" : true, 74 | "avatar_large" : "http://tp1.sinaimg.cn/1404376560/180/0/1", 75 | "verified_reason" : "", 76 | "follow_me" : false, 77 | "online_status" : 0, 78 | "bi_followers_count" : 215 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /src/test/java/org/springframework/social/weibo/api/impl/UserTemplateTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api.impl; 17 | 18 | import static org.springframework.http.HttpMethod.GET; 19 | import static org.springframework.social.test.client.RequestMatchers.method; 20 | import static org.springframework.social.test.client.RequestMatchers.requestTo; 21 | import static org.springframework.social.test.client.ResponseCreators.withResponse; 22 | import static org.springframework.social.weibo.api.impl.WeiboProfileMatcher.verifyWeiboProfile; 23 | 24 | import org.junit.Test; 25 | import org.springframework.social.weibo.api.WeiboProfile; 26 | 27 | public class UserTemplateTest extends AbstractWeiboOperationsTest { 28 | 29 | private UserTemplate userTemplate; 30 | 31 | @Override 32 | public void setUp() { 33 | userTemplate = new UserTemplate(getObjectMapper(), getRestTemplate(), 34 | true); 35 | } 36 | 37 | @Test 38 | public void testGetUserProfileByDomainName() { 39 | mockServer 40 | .expect(requestTo("https://api.weibo.com/2/users/domain_show.json?domain=domain")) 41 | .andExpect(method(GET)) 42 | .andRespond( 43 | withResponse(jsonResource("profile"), responseHeaders)); 44 | 45 | WeiboProfile profile = userTemplate 46 | .getUserProfileByDomainName("domain"); 47 | verifyWeiboProfile(profile); 48 | } 49 | 50 | @Test 51 | public void testGetUserProfileById() { 52 | long uid = 123L; 53 | mockServer 54 | .expect(requestTo("https://api.weibo.com/2/users/show.json?uid=" 55 | + uid)) 56 | .andExpect(method(GET)) 57 | .andRespond( 58 | withResponse(jsonResource("profile"), responseHeaders)); 59 | 60 | WeiboProfile profile = userTemplate.getUserProfileById(uid); 61 | verifyWeiboProfile(profile); 62 | } 63 | 64 | @Test 65 | public void testGetUserProfileByScreenName() { 66 | String screenName = "Cirrus_Test1"; 67 | mockServer 68 | .expect(requestTo("https://api.weibo.com/2/users/show.json?screen_name=" 69 | + screenName)) 70 | .andExpect(method(GET)) 71 | .andRespond( 72 | withResponse(jsonResource("profile"), responseHeaders)); 73 | 74 | WeiboProfile profile = userTemplate 75 | .getUserProfileByScreenName(screenName); 76 | verifyWeiboProfile(profile); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/connect/WeiboAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.connect; 17 | 18 | import org.springframework.social.connect.ApiAdapter; 19 | import org.springframework.social.connect.ConnectionValues; 20 | import org.springframework.social.connect.UserProfile; 21 | import org.springframework.social.connect.UserProfileBuilder; 22 | import org.springframework.social.weibo.api.Weibo; 23 | import org.springframework.social.weibo.api.WeiboProfile; 24 | 25 | public class WeiboAdapter implements ApiAdapter { 26 | 27 | @Override 28 | public boolean test(Weibo api) { 29 | return true; 30 | } 31 | 32 | @Override 33 | public void setConnectionValues(Weibo api, ConnectionValues values) { 34 | WeiboProfile weiboProfile = fetchWeiboProfile(api); 35 | values.setProviderUserId(String.valueOf(weiboProfile.getId())); 36 | values.setDisplayName(weiboProfile.getName()); 37 | values.setProfileUrl("http://weibo.com/u/" + weiboProfile.getId()); 38 | values.setImageUrl(weiboProfile.getProfileImageUrl()); 39 | } 40 | 41 | @Override 42 | public UserProfile fetchUserProfile(Weibo api) { 43 | WeiboProfile weiboProfile = fetchWeiboProfile(api); 44 | String name = weiboProfile.getName(); 45 | return new UserProfileBuilder() 46 | .setUsername(weiboProfile.getScreenName()).setName(name) 47 | .setLastName(extractChineseLastName(name)) 48 | .setFirstName(extractChineseFirstname(name)).build(); 49 | 50 | } 51 | 52 | private String extractChineseFirstname(String name) { 53 | String result = null; 54 | if (name != null && !name.trim().isEmpty()) { 55 | result = name.substring(1); 56 | } 57 | return result; 58 | } 59 | 60 | private String extractChineseLastName(String name) { 61 | String result = null; 62 | if (name != null && !name.trim().isEmpty()) { 63 | result = name.substring(0, 1); 64 | } 65 | return result; 66 | } 67 | 68 | private WeiboProfile fetchWeiboProfile(Weibo api) { 69 | long uid = api.accountOperations().getUid(); 70 | WeiboProfile weiboProfile = api.userOperations() 71 | .getUserProfileById(uid); 72 | return weiboProfile; 73 | } 74 | 75 | @Override 76 | public void updateStatus(Weibo api, String message) { 77 | api.timelineOperations().updateStatus(message); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/Favorite.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api; 17 | 18 | import java.util.Date; 19 | import java.util.List; 20 | 21 | public class Favorite { 22 | 23 | public static class Tag { 24 | private long id; 25 | private String value; 26 | private int count; 27 | 28 | /** 29 | * @return the id 30 | */ 31 | public long getId() { 32 | return id; 33 | } 34 | 35 | /** 36 | * @param id 37 | * the id to set 38 | */ 39 | public void setId(long id) { 40 | this.id = id; 41 | } 42 | 43 | /** 44 | * @return the value 45 | */ 46 | public String getValue() { 47 | return value; 48 | } 49 | 50 | /** 51 | * @param value 52 | * the value to set 53 | */ 54 | public void setValue(String value) { 55 | this.value = value; 56 | } 57 | 58 | /** 59 | * @return the count 60 | */ 61 | public int getCount() { 62 | return count; 63 | } 64 | 65 | /** 66 | * @param count 67 | * the count to set 68 | */ 69 | public void setCount(int count) { 70 | this.count = count; 71 | } 72 | } 73 | 74 | private Status status; 75 | private List tags; 76 | private Date favoritedTime; 77 | 78 | /** 79 | * @return the status 80 | */ 81 | public Status getStatus() { 82 | return status; 83 | } 84 | 85 | /** 86 | * @param status 87 | * the status to set 88 | */ 89 | public void setStatus(Status status) { 90 | this.status = status; 91 | } 92 | 93 | /** 94 | * @return the tags 95 | */ 96 | public List getTags() { 97 | return tags; 98 | } 99 | 100 | /** 101 | * @param tags 102 | * the tags to set 103 | */ 104 | public void setTags(List tags) { 105 | this.tags = tags; 106 | } 107 | 108 | /** 109 | * @return the favoritedTime 110 | */ 111 | public Date getFavoritedTime() { 112 | return favoritedTime; 113 | } 114 | 115 | /** 116 | * @param favoritedTime 117 | * the favoritedTime to set 118 | */ 119 | public void setFavoritedTime(Date favoritedTime) { 120 | this.favoritedTime = favoritedTime; 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /src/test/resources/json/repost.json: -------------------------------------------------------------------------------- 1 | { 2 | "created_at" : "Tue May 31 17:46:55 +0800 2011", 3 | "id" : 11488058246, 4 | "text" : "我是法国人", 5 | "source" : "Sina microblogging", 6 | "favorited" : false, 7 | "truncated" : false, 8 | "in_reply_to_status_id" : "", 9 | "in_reply_to_user_id" : "", 10 | "in_reply_to_screen_name" : "", 11 | "mid" : "5612814510546515491", 12 | "reposts_count" : 8, 13 | "comments_count" : 9, 14 | "geo" : null, 15 | "user" : { 16 | "id" : 1404376560, 17 | "screen_name" : "zaku", 18 | "name" : "zaku", 19 | "province" : "11", 20 | "city" : "5", 21 | "location" : "Beijing's Chaoyang District.", 22 | "description" : "Fifty years of life, is dreamlike; give birth, then you have died, warrior What else regret.", 23 | "url" : "http://blog.sina.com.cn/zaku", 24 | "profile_image_url" : "http://tp1.sinaimg.cn/1404376560/50/0/1", 25 | "domain" : "zaku", 26 | "gender" : "m", 27 | "followers_count" : 1204, 28 | "friends_count" : 447, 29 | "statuses_count" : 2908, 30 | "favourites_count" : 0, 31 | "created_at" : "Fri Aug 28 00:00:00 +0800 2009", 32 | "following" : false, 33 | "allow_all_act_msg" : false, 34 | "remark" : "", 35 | "geo_enabled" : true, 36 | "verified" : false, 37 | "allow_all_comment" : true, 38 | "avatar_large" : "http://tp1.sinaimg.cn/1404376560/180/0/1", 39 | "verified_reason" : "", 40 | "follow_me" : false, 41 | "online_status" : 0, 42 | "bi_followers_count" : 215 43 | }, 44 | "retweeted_status" : { 45 | "created_at" : "Tue May 24 18:04:53 +0800 2011", 46 | "id" : 11142488790, 47 | "text" : "你好", 48 | "source" : "新浪微博", 49 | "favorited" : false, 50 | "truncated" : false, 51 | "in_reply_to_status_id" : "", 52 | "in_reply_to_user_id" : "", 53 | "in_reply_to_screen_name" : "", 54 | "geo" : null, 55 | "mid" : "5610221544300749636", 56 | "annotations" : [], 57 | "reposts_count" : 5, 58 | "comments_count" : 8, 59 | "user" : { 60 | "id" : 1073880650, 61 | "screen_name" : "檀木幻想", 62 | "name" : "檀木幻想", 63 | "province" : "11", 64 | "city" : "5", 65 | "location" : "北京 朝阳区", 66 | "description" : "请访问微博分析家。", 67 | "url" : "http://www.weibo007.com/", 68 | "profile_image_url" : "http://tp3.sinaimg.cn/1073880650/50/1285051202/1", 69 | "domain" : "woodfantasy", 70 | "gender" : "m", 71 | "followers_count" : 723, 72 | "friends_count" : 415, 73 | "statuses_count" : 587, 74 | "favourites_count" : 107, 75 | "created_at" : "Sat Nov 14 00:00:00 +0800 2009", 76 | "following" : true, 77 | "allow_all_act_msg" : true, 78 | "remark" : "", 79 | "geo_enabled" : true, 80 | "verified" : false, 81 | "allow_all_comment" : true, 82 | "avatar_large" : "http://tp3.sinaimg.cn/1073880650/180/1285051202/1", 83 | "verified_reason" : "", 84 | "follow_me" : true, 85 | "online_status" : 0, 86 | "bi_followers_count" : 199 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/impl/json/WeiboProfileMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api.impl.json; 17 | 18 | import java.util.Date; 19 | 20 | import org.codehaus.jackson.annotate.JsonIgnoreProperties; 21 | import org.codehaus.jackson.annotate.JsonProperty; 22 | import org.codehaus.jackson.map.annotate.JsonDeserialize; 23 | import org.springframework.social.weibo.api.Status; 24 | 25 | /** 26 | * Annotated mixin to add Jackson annotations to WeiboProfile. 27 | * 28 | * @author edva8332 29 | */ 30 | @JsonIgnoreProperties(ignoreUnknown = true) 31 | abstract class WeiboProfileMixin { 32 | 33 | WeiboProfileMixin( 34 | @JsonProperty("id") Long id, 35 | @JsonProperty("screen_name") String screenName, 36 | @JsonProperty("name") String name, 37 | @JsonProperty("url") String url, 38 | @JsonProperty("profile_image_url") String profileImageUrl, 39 | @JsonProperty("description") String description, 40 | @JsonProperty("location") String location, 41 | @JsonProperty("created_at") @JsonDeserialize(using = TimelineDateDeserializer.class) Date createAt) { 42 | } 43 | 44 | @JsonProperty("province") 45 | int province; // 省份编码(参考省份编码表) 46 | @JsonProperty("city") 47 | int city; // 城市编码(参考城市编码表) 48 | @JsonProperty("domain") 49 | String domain; // 用户个性化URL 50 | @JsonProperty("gender") 51 | String gender; // 性别,m--男,f--女,n--未知 52 | @JsonProperty("followers_count") 53 | int followersCount; // 粉丝数 54 | @JsonProperty("friends_count") 55 | int friendsCount; // 关注数 56 | @JsonProperty("statuses_count") 57 | int statusesCount; // 微博数 58 | @JsonProperty("favourites_count") 59 | int favouritesCount; // 收藏数 60 | @JsonProperty("following") 61 | boolean following; // 保留字段,是否已关注(此特性暂不支持) 62 | @JsonProperty("verified") 63 | boolean verified; // 加V标示,是否微博认证用户 64 | @JsonProperty("verified_reason") 65 | int verifiedReason; // 认证类型 66 | @JsonProperty("allow_all_act_msg") 67 | boolean allowAllActMsg; // 是否允许所有人给我发私信 68 | @JsonProperty("allow_all_comment") 69 | boolean allowAllComment; // 是否允许所有人对我的微博进行评论 70 | @JsonProperty("follow_me") 71 | boolean followMe; // 此用户是否关注我 72 | @JsonProperty("avatar_large") 73 | String avatarLarge; // 大头像地址 74 | @JsonProperty("online_status") 75 | int onlineStatus; // 用户在线状态 76 | @JsonProperty("status") 77 | Status status; // 用户最新一条微博 78 | @JsonProperty("bi_followers_count") 79 | int biFollowersCount; // 互粉数 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/Trends.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api; 17 | 18 | import java.util.Date; 19 | import java.util.LinkedList; 20 | import java.util.List; 21 | 22 | public class Trends { 23 | 24 | public static class Trend { 25 | private String name; 26 | private String query; 27 | private long amount; 28 | private long delta; 29 | 30 | /** 31 | * @return the name 32 | */ 33 | public String getName() { 34 | return name; 35 | } 36 | 37 | /** 38 | * @param name 39 | * the name to set 40 | */ 41 | public void setName(String name) { 42 | this.name = name; 43 | } 44 | 45 | /** 46 | * @return the query 47 | */ 48 | public String getQuery() { 49 | return query; 50 | } 51 | 52 | /** 53 | * @param query 54 | * the query to set 55 | */ 56 | public void setQuery(String query) { 57 | this.query = query; 58 | } 59 | 60 | /** 61 | * @return the amount 62 | */ 63 | public long getAmount() { 64 | return amount; 65 | } 66 | 67 | /** 68 | * @param amount 69 | * the amount to set 70 | */ 71 | public void setAmount(long amount) { 72 | this.amount = amount; 73 | } 74 | 75 | /** 76 | * @return the delta 77 | */ 78 | public long getDelta() { 79 | return delta; 80 | } 81 | 82 | /** 83 | * @param delta 84 | * the delta to set 85 | */ 86 | public void setDelta(long delta) { 87 | this.delta = delta; 88 | } 89 | } 90 | 91 | private List trends = new LinkedList(); 92 | private Date date; 93 | 94 | /** 95 | * @return the trends 96 | */ 97 | public List getTrends() { 98 | return trends; 99 | } 100 | 101 | /** 102 | * @param trends 103 | * the trends to set 104 | */ 105 | public void setTrends(List trends) { 106 | this.trends = trends; 107 | } 108 | 109 | /** 110 | * @return the date 111 | */ 112 | public Date getDate() { 113 | return date; 114 | } 115 | 116 | /** 117 | * @param date 118 | * the date to set 119 | */ 120 | public void setDate(Date date) { 121 | this.date = date; 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | org.springframework.social 5 | spring-social-weibo 6 | 1.0.1-SNAPSHOT 7 | Spring Social Weibo extension 8 | 9 | 10 | 11 | org.springframework.maven.release 12 | Spring Maven Release Repository 13 | http://maven.springframework.org/release 14 | true 15 | false 16 | 17 | 18 | 19 | org.springframework.maven.snapshot 20 | Spring Maven Snapshot Repository 21 | http://maven.springframework.org/snapshot 22 | false 23 | true 24 | 25 | 26 | 27 | org.springframework.maven.milestone 28 | Spring Maven Milestone Repository 29 | http://maven.springframework.org/milestone 30 | false 31 | 32 | 33 | 34 | 35 | 1.6 36 | UTF-8 37 | 1.0.2.RELEASE 38 | 39 | 40 | 41 | 42 | org.codehaus.jackson 43 | jackson-mapper-asl 44 | 1.9.2 45 | compile 46 | 47 | 48 | org.springframework.social 49 | spring-social-core 50 | ${org.springframework.social-version} 51 | 52 | 53 | org.springframework.social 54 | spring-social-test 55 | ${org.springframework.social-version} 56 | test 57 | 58 | 59 | junit 60 | junit 61 | 4.9 62 | test 63 | 64 | 65 | 66 | 67 | 68 | 69 | org.apache.maven.plugins 70 | maven-compiler-plugin 71 | 2.3.2 72 | 73 | ${java-version} 74 | ${java-version} 75 | ${project.build.sourceEncoding} 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /src/test/resources/json/users.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "id" : 1, 3 | "idstr" : "1", 4 | "screen_name" : "Cirrus_Test1", 5 | "name" : "Cirrus_Test1", 6 | "province" : "11", 7 | "city" : "1000", 8 | "location" : "北京三元桥", 9 | "description" : "description", 10 | "url" : "http://myFavouriteUrl.com", 11 | "profile_image_url" : "http://tp4.sinaimg.cn/2429576463/50/0/1", 12 | "domain" : "domain", 13 | "gender" : "m", 14 | "followers_count" : 1, 15 | "friends_count" : 1, 16 | "statuses_count" : 1, 17 | "favourites_count" : 0, 18 | "created_at" : "Thu Oct 27 00:00:00 +0800 2011", 19 | "following" : false, 20 | "allow_all_act_msg" : false, 21 | "geo_enabled" : true, 22 | "verified" : false, 23 | "verified_type" : -1, 24 | "status" : { 25 | "created_at" : "Thu Oct 27 10:18:29 +0800 2011", 26 | "id" : 3373052761021575, 27 | "mid" : "3373052761021575", 28 | "idstr" : "3373052761021575", 29 | "text" : "Youpie", 30 | "source" : "????????????", 31 | "favorited" : true, 32 | "truncated" : true, 33 | "in_reply_to_status_id" : "", 34 | "in_reply_to_user_id" : "", 35 | "in_reply_to_screen_name" : "", 36 | "geo" : null, 37 | "reposts_count" : 0, 38 | "comments_count" : 0, 39 | "mlevel" : 0 40 | }, 41 | "allow_all_comment" : true, 42 | "avatar_large" : "http://tp4.sinaimg.cn/2429576463/180/0/1", 43 | "verified_reason" : "verified reason", 44 | "follow_me" : false, 45 | "online_status" : 0, 46 | "bi_followers_count" : 1, 47 | "lang" : "zh-cn" 48 | }, 49 | { 50 | "id" : 2, 51 | "idstr" : "2", 52 | "screen_name" : "Cirrus_Test2", 53 | "name" : "Cirrus_Test2", 54 | "province" : "11", 55 | "city" : "1000", 56 | "location" : "北京三元桥", 57 | "description" : "description", 58 | "url" : "http://myFavouriteUrl.com", 59 | "profile_image_url" : "http://tp4.sinaimg.cn/2429576463/50/0/2", 60 | "domain" : "domain", 61 | "gender" : "m", 62 | "followers_count" : 1, 63 | "friends_count" : 1, 64 | "statuses_count" : 1, 65 | "favourites_count" : 0, 66 | "created_at" : "Thu Oct 27 00:00:00 +0800 2011", 67 | "following" : false, 68 | "allow_all_act_msg" : false, 69 | "geo_enabled" : true, 70 | "verified" : false, 71 | "verified_type" : -1, 72 | "status" : { 73 | "created_at" : "Thu Nov 10 10:18:29 +0800 2011", 74 | "id" : 3373052761021575, 75 | "mid" : "3373052761021575", 76 | "idstr" : "3373052761021575", 77 | "text" : "Youpie", 78 | "source" : "????????????", 79 | "favorited" : true, 80 | "truncated" : true, 81 | "in_reply_to_status_id" : "", 82 | "in_reply_to_user_id" : "", 83 | "in_reply_to_screen_name" : "", 84 | "geo" : null, 85 | "reposts_count" : 0, 86 | "comments_count" : 0, 87 | "mlevel" : 0 88 | }, 89 | "allow_all_comment" : true, 90 | "avatar_large" : "http://tp4.sinaimg.cn/2429576463/180/0/2", 91 | "verified_reason" : "verified reason", 92 | "follow_me" : false, 93 | "online_status" : 0, 94 | "bi_followers_count" : 1, 95 | "lang" : "zh-cn" 96 | } 97 | ] 98 | -------------------------------------------------------------------------------- /src/test/resources/json/cursoredUsers.json: -------------------------------------------------------------------------------- 1 | { 2 | "users" : [{ 3 | "id" : 1, 4 | "idstr" : "1", 5 | "screen_name" : "Cirrus_Test1", 6 | "name" : "Cirrus_Test1", 7 | "province" : "11", 8 | "city" : "1000", 9 | "location" : "北京三元桥", 10 | "description" : "description", 11 | "url" : "http://myFavouriteUrl.com", 12 | "profile_image_url" : "http://tp4.sinaimg.cn/2429576463/50/0/1", 13 | "domain" : "domain", 14 | "gender" : "m", 15 | "followers_count" : 1, 16 | "friends_count" : 1, 17 | "statuses_count" : 1, 18 | "favourites_count" : 0, 19 | "created_at" : "Thu Oct 27 00:00:00 +0800 2011", 20 | "following" : false, 21 | "allow_all_act_msg" : false, 22 | "geo_enabled" : true, 23 | "verified" : false, 24 | "verified_type" : -1, 25 | "status" : { 26 | "created_at" : "Thu Oct 27 10:18:29 +0800 2011", 27 | "id" : 3373052761021575, 28 | "mid" : "3373052761021575", 29 | "idstr" : "3373052761021575", 30 | "text" : "Youpie", 31 | "source" : "????????????", 32 | "favorited" : true, 33 | "truncated" : true, 34 | "in_reply_to_status_id" : "", 35 | "in_reply_to_user_id" : "", 36 | "in_reply_to_screen_name" : "", 37 | "geo" : null, 38 | "reposts_count" : 0, 39 | "comments_count" : 0, 40 | "mlevel" : 0 41 | }, 42 | "allow_all_comment" : true, 43 | "avatar_large" : "http://tp4.sinaimg.cn/2429576463/180/0/1", 44 | "verified_reason" : "verified reason", 45 | "follow_me" : false, 46 | "online_status" : 0, 47 | "bi_followers_count" : 1, 48 | "lang" : "zh-cn" 49 | }, 50 | { 51 | "id" : 2, 52 | "idstr" : "2", 53 | "screen_name" : "Cirrus_Test2", 54 | "name" : "Cirrus_Test2", 55 | "province" : "11", 56 | "city" : "1000", 57 | "location" : "北京三元桥", 58 | "description" : "description", 59 | "url" : "http://myFavouriteUrl.com", 60 | "profile_image_url" : "http://tp4.sinaimg.cn/2429576463/50/0/2", 61 | "domain" : "domain", 62 | "gender" : "m", 63 | "followers_count" : 1, 64 | "friends_count" : 1, 65 | "statuses_count" : 1, 66 | "favourites_count" : 0, 67 | "created_at" : "Thu Oct 27 00:00:00 +0800 2011", 68 | "following" : false, 69 | "allow_all_act_msg" : false, 70 | "geo_enabled" : true, 71 | "verified" : false, 72 | "verified_type" : -1, 73 | "status" : { 74 | "created_at" : "Thu Nov 10 10:18:29 +0800 2011", 75 | "id" : 3373052761021575, 76 | "mid" : "3373052761021575", 77 | "idstr" : "3373052761021575", 78 | "text" : "Youpie", 79 | "source" : "????????????", 80 | "favorited" : true, 81 | "truncated" : true, 82 | "in_reply_to_status_id" : "", 83 | "in_reply_to_user_id" : "", 84 | "in_reply_to_screen_name" : "", 85 | "geo" : null, 86 | "reposts_count" : 0, 87 | "comments_count" : 0, 88 | "mlevel" : 0 89 | }, 90 | "allow_all_comment" : true, 91 | "avatar_large" : "http://tp4.sinaimg.cn/2429576463/180/0/2", 92 | "verified_reason" : "verified reason", 93 | "follow_me" : false, 94 | "online_status" : 0, 95 | "bi_followers_count" : 1, 96 | "lang" : "zh-cn" 97 | } 98 | ], 99 | "next_cursor" : 1, 100 | "previous_cursor" : 0, 101 | "total_number" : 650 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/Comment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api; 17 | 18 | import java.util.Date; 19 | 20 | public class Comment { 21 | 22 | private long id; 23 | private Date createdAt; 24 | private String text; 25 | private String source; 26 | private String mid; 27 | private WeiboProfile user; 28 | private Status status; 29 | 30 | public Comment(long id, Date createdAt, String text, String source) { 31 | super(); 32 | this.id = id; 33 | this.createdAt = createdAt; 34 | this.text = text; 35 | this.source = source; 36 | } 37 | 38 | /** 39 | * @return the id 40 | */ 41 | public long getId() { 42 | return id; 43 | } 44 | 45 | /** 46 | * @param id 47 | * the id to set 48 | */ 49 | public void setId(long id) { 50 | this.id = id; 51 | } 52 | 53 | /** 54 | * @return the createdAt 55 | */ 56 | public Date getCreatedAt() { 57 | return createdAt; 58 | } 59 | 60 | /** 61 | * @param createdAt 62 | * the createdAt to set 63 | */ 64 | public void setCreatedAt(Date createdAt) { 65 | this.createdAt = createdAt; 66 | } 67 | 68 | /** 69 | * @return the text 70 | */ 71 | public String getText() { 72 | return text; 73 | } 74 | 75 | /** 76 | * @param text 77 | * the text to set 78 | */ 79 | public void setText(String text) { 80 | this.text = text; 81 | } 82 | 83 | /** 84 | * @return the source 85 | */ 86 | public String getSource() { 87 | return source; 88 | } 89 | 90 | /** 91 | * @param source 92 | * the source to set 93 | */ 94 | public void setSource(String source) { 95 | this.source = source; 96 | } 97 | 98 | /** 99 | * @return the mid 100 | */ 101 | public String getMid() { 102 | return mid; 103 | } 104 | 105 | /** 106 | * @param mid 107 | * the mid to set 108 | */ 109 | public void setMid(String mid) { 110 | this.mid = mid; 111 | } 112 | 113 | /** 114 | * @return the user 115 | */ 116 | public WeiboProfile getUser() { 117 | return user; 118 | } 119 | 120 | /** 121 | * @param user 122 | * the user to set 123 | */ 124 | public void setUser(WeiboProfile user) { 125 | this.user = user; 126 | } 127 | 128 | /** 129 | * @return the status 130 | */ 131 | public Status getStatus() { 132 | return status; 133 | } 134 | 135 | /** 136 | * @param status 137 | * the status to set 138 | */ 139 | public void setStatus(Status status) { 140 | this.status = status; 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/social/weibo/api/impl/AccountTemplateTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api.impl; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | import static org.springframework.http.HttpMethod.GET; 20 | import static org.springframework.social.test.client.RequestMatchers.method; 21 | import static org.springframework.social.test.client.RequestMatchers.requestTo; 22 | import static org.springframework.social.test.client.ResponseCreators.withResponse; 23 | 24 | import java.util.List; 25 | 26 | import org.junit.Test; 27 | import org.springframework.social.weibo.api.ApiRateLimit; 28 | import org.springframework.social.weibo.api.LimitTimeUnit; 29 | import org.springframework.social.weibo.api.RateLimitStatus; 30 | 31 | public class AccountTemplateTest extends AbstractWeiboOperationsTest { 32 | 33 | private AccountTemplate accountTemplate; 34 | 35 | @Test 36 | public void testGetUid() { 37 | mockServer 38 | .expect(requestTo("https://api.weibo.com/2/account/get_uid.json")) 39 | .andExpect(method(GET)) 40 | .andRespond( 41 | withResponse(jsonResource("account"), responseHeaders)); 42 | assertEquals(123, accountTemplate.getUid()); 43 | } 44 | 45 | @Override 46 | public void setUp() { 47 | accountTemplate = new AccountTemplate(getObjectMapper(), 48 | getRestTemplate(), true); 49 | } 50 | 51 | @Test 52 | public void testGetRateLimitStatus() { 53 | mockServer 54 | .expect(requestTo("https://api.weibo.com/2/account/rate_limit_status.json")) 55 | .andExpect(method(GET)) 56 | .andRespond( 57 | withResponse(jsonResource("rateLimitStatus"), 58 | responseHeaders)); 59 | RateLimitStatus rateLimitStatus = accountTemplate.getRateLimitStatus(); 60 | 61 | List apiRateLimits = rateLimitStatus.getApiRateLimits(); 62 | assertEquals(5, apiRateLimits.size()); 63 | ApiRateLimit updateStatusRateLimit = apiRateLimits.iterator().next(); 64 | assertEquals("/statuses/update", updateStatusRateLimit.getApi()); 65 | assertEquals(30, updateStatusRateLimit.getLimit()); 66 | assertEquals(LimitTimeUnit.HOURS, 67 | updateStatusRateLimit.getLimitTimeUnit()); 68 | assertEquals(10, updateStatusRateLimit.getRemainingHits()); 69 | 70 | assertEquals(1000, rateLimitStatus.getIpLimit()); 71 | assertEquals(LimitTimeUnit.HOURS, rateLimitStatus.getLimitTimeUnit()); 72 | assertEquals(999, rateLimitStatus.getRemainingIpHits()); 73 | assertEquals(149, rateLimitStatus.getRemainingUserHits()); 74 | assertEquals(1330509600000L, rateLimitStatus.getResetTime().getTime()); 75 | assertEquals(2295, rateLimitStatus.getResetTimeInSeconds()); 76 | assertEquals(150, rateLimitStatus.getUserLimit()); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/resources/json/timeline.json: -------------------------------------------------------------------------------- 1 | { 2 | "statuses" : [{ 3 | "created_at" : "Tue May 31 17:46:55 +0800 2011", 4 | "id" : 11488058246, 5 | "text" : "你好", 6 | "source" : "Sina microblogging", 7 | "favorited" : false, 8 | "truncated" : false, 9 | "in_reply_to_status_id" : "", 10 | "in_reply_to_user_id" : "", 11 | "in_reply_to_screen_name" : "", 12 | "mid" : "5612814510546515491", 13 | "reposts_count" : 8, 14 | "comments_count" : 9, 15 | "geo" : null, 16 | "user" : { 17 | "id" : 1404376560, 18 | "screen_name" : "zaku", 19 | "name" : "zaku", 20 | "province" : "11", 21 | "city" : "5", 22 | "location" : "Beijing's Chaoyang District.", 23 | "description" : "Fifty years of life, is dreamlike; give birth, then you have died, warrior What else regret.", 24 | "url" : "http://blog.sina.com.cn/zaku", 25 | "profile_image_url" : "http://tp1.sinaimg.cn/1404376560/50/0/1", 26 | "domain" : "zaku", 27 | "gender" : "m", 28 | "followers_count" : 1204, 29 | "friends_count" : 447, 30 | "statuses_count" : 2908, 31 | "favourites_count" : 0, 32 | "created_at" : "Fri Aug 28 00:00:00 +0800 2009", 33 | "following" : false, 34 | "allow_all_act_msg" : false, 35 | "remark" : "", 36 | "geo_enabled" : true, 37 | "verified" : false, 38 | "allow_all_comment" : true, 39 | "avatar_large" : "http://tp1.sinaimg.cn/1404376560/180/0/1", 40 | "verified_reason" : "", 41 | "follow_me" : false, 42 | "online_status" : 0, 43 | "bi_followers_count" : 215 44 | } 45 | }, { 46 | "created_at" : "Tue May 31 17:56:55 +0800 2011", 47 | "id" : 11488058246, 48 | "text" : "seeking attention.", 49 | "source" : "Sina microblogging", 50 | "favorited" : false, 51 | "truncated" : false, 52 | "in_reply_to_status_id" : "", 53 | "in_reply_to_user_id" : "", 54 | "in_reply_to_screen_name" : "", 55 | "geo" : null, 56 | "mid" : "5612814510546515491", 57 | "reposts_count" : 8, 58 | "comments_count" : 9, 59 | "annotations" : [{ 60 | "server_ip" : "10.75.15.190" 61 | } 62 | ], 63 | "geo" : { 64 | "type" : "Point", 65 | "coordinates" : [30.722584, 104.033936] 66 | }, 67 | "user" : { 68 | "id" : 1404376560, 69 | "screen_name" : "zaku", 70 | "name" : "zaku", 71 | "province" : "11", 72 | "city" : "5", 73 | "location" : "Beijing's Chaoyang District.", 74 | "description" : "Fifty years of life, is dreamlike; give birth, then you have died, warrior What else regret.", 75 | "url" : "http://blog.sina.com.cn/zaku", 76 | "profile_image_url" : "http://tp1.sinaimg.cn/1404376560/50/0/1", 77 | "domain" : "zaku", 78 | "gender" : "m", 79 | "followers_count" : 1204, 80 | "friends_count" : 447, 81 | "statuses_count" : 2908, 82 | "favourites_count" : 0, 83 | "created_at" : "Fri Aug 28 00:00:00 +0800 2009", 84 | "following" : false, 85 | "allow_all_act_msg" : false, 86 | "remark" : "", 87 | "geo_enabled" : true, 88 | "verified" : false, 89 | "allow_all_comment" : true, 90 | "avatar_large" : "http://tp1.sinaimg.cn/1404376560/180/0/1", 91 | "verified_reason" : "", 92 | "follow_me" : false, 93 | "online_status" : 0, 94 | "bi_followers_count" : 215 95 | } 96 | } 97 | ], 98 | "previous_cursor" : 0, 99 | "next_cursor" : 1, 100 | "total_number" : 81655 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/CommentOperations.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api; 17 | 18 | import java.util.List; 19 | 20 | public interface CommentOperations { 21 | 22 | Comment createComment(long id, String comment); 23 | 24 | Comment createComment(long id, String comment, 25 | boolean commentFromExternalSource); 26 | 27 | Comment deleteComment(long id); 28 | 29 | List deleteComments(List ids); 30 | 31 | CursoredList getCommentsByMe(); 32 | 33 | CursoredList getCommentsByMe(int pageSize, int pageNumber); 34 | 35 | CursoredList getCommentsByMe(int pageSize, int pageNumber, 36 | SourceFilterType sourceFilterType); 37 | 38 | CursoredList getCommentsByMe(long sinceId, long maxId, 39 | int pageSize, int pageNumber, SourceFilterType sourceFilterType); 40 | 41 | CursoredList getMentioningComments(); 42 | 43 | CursoredList getMentioningComments(int pageSize, int pageNumber); 44 | 45 | CursoredList getMentioningComments(int pageSize, int pageNumber, 46 | AuthorFilterType authorFilterType, SourceFilterType sourceFilterType); 47 | 48 | CursoredList getMentioningComments(long sinceId, long maxId, 49 | int pageSize, int pageNumber, AuthorFilterType authorFilterType, 50 | SourceFilterType sourceFilterType); 51 | 52 | CursoredList getCommentsOnStatus(long id); 53 | 54 | CursoredList getCommentsOnStatus(long id, int pageSize, 55 | int pageNumber); 56 | 57 | CursoredList getCommentsOnStatus(long id, int pageSize, 58 | int pageNumber, AuthorFilterType authorFilterType); 59 | 60 | CursoredList getCommentsOnStatus(long id, long sinceId, 61 | long maxId, int pageSize, int pageNumber, 62 | AuthorFilterType authorFilterType); 63 | 64 | /** 65 | * @deprecated This API seems buggy, no result is returned 66 | * @param ids 67 | * @return 68 | */ 69 | @Deprecated 70 | List getCommentsOnStatuses(List ids); 71 | 72 | CursoredList getCommentsTimeline(); 73 | 74 | CursoredList getCommentsTimeline(int pageSize, int pageNumber); 75 | 76 | CursoredList getCommentsTimeline(long sinceId, long maxId, 77 | int pageSize, int pageNumber); 78 | 79 | CursoredList getCommentsToMe(); 80 | 81 | CursoredList getCommentsToMe(int pageSize, int pageNumber); 82 | 83 | CursoredList getCommentsToMe(int pageSize, int pageNumber, 84 | AuthorFilterType authorFilterType, SourceFilterType sourceFilterType); 85 | 86 | CursoredList getCommentsToMe(long sinceId, long maxId, 87 | int pageSize, int pageNumber, AuthorFilterType authorFilterType, 88 | SourceFilterType sourceFilterType); 89 | 90 | Comment replyComment(long commentId, long statusId, String comment); 91 | 92 | Comment replyComment(long commentId, long statusId, String comment, 93 | boolean withoutMention, boolean commentFromExternalSource); 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/test/resources/json/cursoredFavorites.json: -------------------------------------------------------------------------------- 1 | { 2 | "favorites" : [{ 3 | "status" : { 4 | "created_at" : "Tue May 31 17:46:55 +0800 2011", 5 | "id" : 11488058246, 6 | "text" : "求关注。", 7 | "source" : "新浪微博", 8 | "favorited" : false, 9 | "truncated" : false, 10 | "in_reply_to_status_id" : "", 11 | "in_reply_to_user_id" : "", 12 | "in_reply_to_screen_name" : "", 13 | "geo" : null, 14 | "mid" : "5612814510546515491", 15 | "reposts_count" : 8, 16 | "comments_count" : 9, 17 | "annotations" : [], 18 | "user" : { 19 | "id" : 1404376560, 20 | "screen_name" : "zaku", 21 | "name" : "zaku", 22 | "province" : "11", 23 | "city" : "5", 24 | "location" : "北京 朝阳区", 25 | "description" : "人生五十年,乃如梦如幻;有生斯有死,壮士复何憾。", 26 | "url" : "http://blog.sina.com.cn/zaku", 27 | "profile_image_url" : "http://tp1.sinaimg.cn/1404376560/50/0/1", 28 | "domain" : "zaku", 29 | "gender" : "m", 30 | "followers_count" : 1204, 31 | "friends_count" : 447, 32 | "statuses_count" : 2908, 33 | "favourites_count" : 0, 34 | "created_at" : "Fri Aug 28 00:00:00 +0800 2009", 35 | "following" : false, 36 | "allow_all_act_msg" : false, 37 | "remark" : "", 38 | "geo_enabled" : true, 39 | "verified" : false, 40 | "allow_all_comment" : true, 41 | "avatar_large" : "http://tp1.sinaimg.cn/1404376560/180/0/1", 42 | "verified_reason" : "", 43 | "follow_me" : false, 44 | "online_status" : 0, 45 | "bi_followers_count" : 215 46 | } 47 | }, 48 | "tags" : [{ 49 | "id" : 23, 50 | "tag" : "80后", 51 | "count" : 25369 52 | }, { 53 | "id" : 24, 54 | "tag" : "80后" 55 | } 56 | ], 57 | "favorited_time" : "Thu Jun 02 15:16:16 +0800 2011" 58 | }, { 59 | "status" : { 60 | "created_at" : "Tue May 31 17:46:55 +0800 2011", 61 | "id" : 11488058246, 62 | "text" : "求关注。", 63 | "source" : "新浪微博", 64 | "favorited" : false, 65 | "truncated" : false, 66 | "in_reply_to_status_id" : "", 67 | "in_reply_to_user_id" : "", 68 | "in_reply_to_screen_name" : "", 69 | "geo" : null, 70 | "mid" : "5612814510546515491", 71 | "reposts_count" : 8, 72 | "comments_count" : 9, 73 | "annotations" : [], 74 | "user" : { 75 | "id" : 1404376560, 76 | "screen_name" : "zaku", 77 | "name" : "zaku", 78 | "province" : "11", 79 | "city" : "5", 80 | "location" : "北京 朝阳区", 81 | "description" : "人生五十年,乃如梦如幻;有生斯有死,壮士复何憾。", 82 | "url" : "http://blog.sina.com.cn/zaku", 83 | "profile_image_url" : "http://tp1.sinaimg.cn/1404376560/50/0/1", 84 | "domain" : "zaku", 85 | "gender" : "m", 86 | "followers_count" : 1204, 87 | "friends_count" : 447, 88 | "statuses_count" : 2908, 89 | "favourites_count" : 0, 90 | "created_at" : "Fri Aug 28 00:00:00 +0800 2009", 91 | "following" : false, 92 | "allow_all_act_msg" : false, 93 | "remark" : "", 94 | "geo_enabled" : true, 95 | "verified" : false, 96 | "allow_all_comment" : true, 97 | "avatar_large" : "http://tp1.sinaimg.cn/1404376560/180/0/1", 98 | "verified_reason" : "", 99 | "follow_me" : false, 100 | "online_status" : 0, 101 | "bi_followers_count" : 215 102 | } 103 | }, 104 | "tags" : [{ 105 | "id" : 23, 106 | "tag" : "80后" 107 | }, { 108 | "id" : 24, 109 | "tag" : "youpie" 110 | } 111 | ], 112 | "favorited_time" : "Thu Jun 02 15:16:16 +0800 2011" 113 | } 114 | 115 | ], 116 | "total_number" : 16 117 | } 118 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/impl/json/TrendsDeserializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api.impl.json; 17 | 18 | import java.io.IOException; 19 | import java.text.ParseException; 20 | import java.text.SimpleDateFormat; 21 | import java.util.Comparator; 22 | import java.util.Iterator; 23 | import java.util.Map.Entry; 24 | import java.util.SortedSet; 25 | import java.util.TreeSet; 26 | 27 | import org.apache.commons.logging.Log; 28 | import org.apache.commons.logging.LogFactory; 29 | import org.codehaus.jackson.JsonNode; 30 | import org.codehaus.jackson.JsonParser; 31 | import org.codehaus.jackson.JsonProcessingException; 32 | import org.codehaus.jackson.map.DeserializationContext; 33 | import org.codehaus.jackson.map.JsonDeserializer; 34 | import org.springframework.social.weibo.api.Trends; 35 | import org.springframework.social.weibo.api.Trends.Trend; 36 | 37 | public class TrendsDeserializer extends JsonDeserializer> { 38 | 39 | private static final Log logger = LogFactory 40 | .getLog(TrendsDeserializer.class.getName()); 41 | 42 | private static final Comparator comparator = new Comparator() { 43 | 44 | @Override 45 | public int compare(Trends o1, Trends o2) { 46 | if (o1.getDate() == null) { 47 | if (o2.getDate() == null) { 48 | return 0; 49 | } else { 50 | return 1; 51 | } 52 | } else if (o2.getDate() == null) { 53 | return -1; 54 | } else { 55 | return o1.getDate().compareTo(o2.getDate()); 56 | } 57 | } 58 | }; 59 | 60 | @Override 61 | public SortedSet deserialize(JsonParser jp, 62 | DeserializationContext ctxt) throws IOException, 63 | JsonProcessingException { 64 | SimpleDateFormat dateFormat = new SimpleDateFormat(); 65 | TreeSet result = new TreeSet(comparator); 66 | for (Iterator> iterator = jp.readValueAsTree() 67 | .getFields(); iterator.hasNext();) { 68 | Entry next = iterator.next(); 69 | Trends trends = new Trends(); 70 | try { 71 | dateFormat 72 | .applyPattern(retrieveDateFormatPattern(next.getKey())); 73 | trends.setDate(dateFormat.parse(next.getKey())); 74 | JsonNode trendsNode = next.getValue(); 75 | for (Iterator iterator2 = trendsNode.getElements(); iterator2 76 | .hasNext();) { 77 | JsonParser nodeParser = iterator2.next().traverse(); 78 | nodeParser.setCodec(jp.getCodec()); 79 | Trend readValueAs = nodeParser.readValueAs(Trend.class); 80 | trends.getTrends().add(readValueAs); 81 | } 82 | result.add(trends); 83 | } catch (ParseException e) { 84 | logger.warn("Unable to parse date", e); 85 | } 86 | } 87 | return result; 88 | } 89 | 90 | private String retrieveDateFormatPattern(String key) { 91 | String result = null; 92 | switch (key.length()) { 93 | case 19: 94 | result = "yyyy-MM-dd HH:mm:ss"; 95 | break; 96 | case 16: 97 | result = "yyyy-MM-dd HH:mm"; 98 | break; 99 | default: 100 | result = "yyyy-MM-dd"; 101 | break; 102 | } 103 | return result; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/RateLimitStatus.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api; 17 | 18 | import java.util.Date; 19 | import java.util.List; 20 | 21 | public class RateLimitStatus { 22 | 23 | private int ipLimit; 24 | private LimitTimeUnit limitTimeUnit; 25 | private int remainingIpHits; 26 | private int remainingUserHits; 27 | private Date resetTime; 28 | private int resetTimeInSeconds; 29 | private int userLimit; 30 | private List apiRateLimits; 31 | 32 | /** 33 | * @return the ipLimit 34 | */ 35 | public int getIpLimit() { 36 | return ipLimit; 37 | } 38 | 39 | /** 40 | * @param ipLimit 41 | * the ipLimit to set 42 | */ 43 | public void setIpLimit(int ipLimit) { 44 | this.ipLimit = ipLimit; 45 | } 46 | 47 | /** 48 | * @return the limitTimeUnit 49 | */ 50 | public LimitTimeUnit getLimitTimeUnit() { 51 | return limitTimeUnit; 52 | } 53 | 54 | /** 55 | * @param limitTimeUnit 56 | * the limitTimeUnit to set 57 | */ 58 | public void setLimitTimeUnit(LimitTimeUnit limitTimeUnit) { 59 | this.limitTimeUnit = limitTimeUnit; 60 | } 61 | 62 | /** 63 | * @return the remainingIpHits 64 | */ 65 | public int getRemainingIpHits() { 66 | return remainingIpHits; 67 | } 68 | 69 | /** 70 | * @param remainingIpHits 71 | * the remainingIpHits to set 72 | */ 73 | public void setRemainingIpHits(int remainingIpHits) { 74 | this.remainingIpHits = remainingIpHits; 75 | } 76 | 77 | /** 78 | * @return the remainingUserHits 79 | */ 80 | public int getRemainingUserHits() { 81 | return remainingUserHits; 82 | } 83 | 84 | /** 85 | * @param remainingUserHits 86 | * the remainingUserHits to set 87 | */ 88 | public void setRemainingUserHits(int remainingUserHits) { 89 | this.remainingUserHits = remainingUserHits; 90 | } 91 | 92 | /** 93 | * @return the resetTime 94 | */ 95 | public Date getResetTime() { 96 | return resetTime; 97 | } 98 | 99 | /** 100 | * @param resetTime 101 | * the resetTime to set 102 | */ 103 | public void setResetTime(Date resetTime) { 104 | this.resetTime = resetTime; 105 | } 106 | 107 | /** 108 | * @return the resetTimeInSeconds 109 | */ 110 | public int getResetTimeInSeconds() { 111 | return resetTimeInSeconds; 112 | } 113 | 114 | /** 115 | * @param resetTimeInSeconds 116 | * the resetTimeInSeconds to set 117 | */ 118 | public void setResetTimeInSeconds(int resetTimeInSeconds) { 119 | this.resetTimeInSeconds = resetTimeInSeconds; 120 | } 121 | 122 | /** 123 | * @return the userLimit 124 | */ 125 | public int getUserLimit() { 126 | return userLimit; 127 | } 128 | 129 | /** 130 | * @param userLimit 131 | * the userLimit to set 132 | */ 133 | public void setUserLimit(int userLimit) { 134 | this.userLimit = userLimit; 135 | } 136 | 137 | /** 138 | * @return the apiRateLimits 139 | */ 140 | public List getApiRateLimits() { 141 | return apiRateLimits; 142 | } 143 | 144 | /** 145 | * @param apiRateLimits 146 | * the apiRateLimits to set 147 | */ 148 | public void setApiRateLimits(List apiRateLimits) { 149 | this.apiRateLimits = apiRateLimits; 150 | } 151 | 152 | } 153 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/connect/WeiboOAuth2Template.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.connect; 17 | 18 | import java.io.IOException; 19 | import java.util.Collections; 20 | import java.util.LinkedHashMap; 21 | import java.util.Map; 22 | import java.util.Map.Entry; 23 | 24 | import org.apache.commons.logging.Log; 25 | import org.apache.commons.logging.LogFactory; 26 | import org.codehaus.jackson.map.ObjectMapper; 27 | import org.codehaus.jackson.type.TypeReference; 28 | import org.springframework.http.HttpInputMessage; 29 | import org.springframework.http.MediaType; 30 | import org.springframework.http.converter.FormHttpMessageConverter; 31 | import org.springframework.http.converter.HttpMessageConverter; 32 | import org.springframework.http.converter.HttpMessageNotReadableException; 33 | import org.springframework.social.oauth2.AccessGrant; 34 | import org.springframework.social.oauth2.OAuth2Template; 35 | import org.springframework.social.support.ClientHttpRequestFactorySelector; 36 | import org.springframework.util.LinkedMultiValueMap; 37 | import org.springframework.util.MultiValueMap; 38 | import org.springframework.web.client.RestTemplate; 39 | 40 | public class WeiboOAuth2Template extends OAuth2Template { 41 | 42 | private static final Log logger = LogFactory 43 | .getLog(WeiboOAuth2Template.class.getName()); 44 | 45 | public WeiboOAuth2Template(String clientId, String clientSecret) { 46 | super(clientId, clientSecret, 47 | "https://api.t.sina.com.cn/oauth2/authorize", 48 | "https://api.t.sina.com.cn/oauth2/access_token"); 49 | } 50 | 51 | @Override 52 | protected RestTemplate createRestTemplate() { 53 | RestTemplate restTemplate = new RestTemplate( 54 | ClientHttpRequestFactorySelector.getRequestFactory()); 55 | HttpMessageConverter messageConverter = new FormHttpMessageConverter() { 56 | 57 | private final ObjectMapper objectMapper = new ObjectMapper(); 58 | 59 | @Override 60 | public boolean canRead(Class clazz, MediaType mediaType) { 61 | return true; 62 | } 63 | 64 | @Override 65 | public MultiValueMap read( 66 | Class> clazz, 67 | HttpInputMessage inputMessage) throws IOException, 68 | HttpMessageNotReadableException { 69 | 70 | TypeReference> mapType = new TypeReference>() { 71 | }; 72 | LinkedHashMap readValue = objectMapper.readValue( 73 | inputMessage.getBody(), mapType); 74 | LinkedMultiValueMap result = new LinkedMultiValueMap(); 75 | for (Entry currentEntry : readValue.entrySet()) { 76 | result.add(currentEntry.getKey(), currentEntry.getValue() 77 | .toString()); 78 | } 79 | return result; 80 | } 81 | }; 82 | 83 | restTemplate.setMessageConverters(Collections 84 | .> singletonList(messageConverter)); 85 | return restTemplate; 86 | } 87 | 88 | @Override 89 | @SuppressWarnings("unchecked") 90 | protected AccessGrant postForAccessGrant(String accessTokenUrl, 91 | MultiValueMap parameters) { 92 | MultiValueMap response = getRestTemplate() 93 | .postForObject(accessTokenUrl, parameters, MultiValueMap.class); 94 | String expires = response.getFirst("expires_in"); 95 | String accessToken = response.getFirst("access_token"); 96 | if (logger.isDebugEnabled()) { 97 | logger.debug("access token value = " + accessToken); 98 | } 99 | return new AccessGrant(accessToken, null, null, 100 | expires != null ? Integer.valueOf(expires) : null); 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/Status.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api; 17 | 18 | import java.util.Date; 19 | 20 | public class Status { 21 | 22 | private Long id; 23 | private Date createdAt; 24 | private String text; 25 | private String source; 26 | private boolean favorited; 27 | private boolean truncated; 28 | private String inReplyToStatusId; 29 | private String inReplyToUserId; 30 | private String inReplyToScreenName; 31 | private String mid; 32 | private WeiboProfile user; 33 | private int repostsCount; 34 | private int commentsCount; 35 | private Status originalStatus; 36 | 37 | public Status(Long id, Date createdAt, String text, String source, 38 | boolean favorited, boolean truncated, int repostsCount, 39 | int commentsCount) { 40 | super(); 41 | this.id = id; 42 | this.createdAt = createdAt; 43 | this.text = text; 44 | this.source = source; 45 | this.favorited = favorited; 46 | this.truncated = truncated; 47 | this.repostsCount = repostsCount; 48 | this.commentsCount = commentsCount; 49 | } 50 | 51 | public Long getId() { 52 | return id; 53 | } 54 | 55 | public void setId(Long id) { 56 | this.id = id; 57 | } 58 | 59 | public Date getCreatedAt() { 60 | return createdAt; 61 | } 62 | 63 | public void setCreatedAt(Date createdAt) { 64 | this.createdAt = createdAt; 65 | } 66 | 67 | public String getText() { 68 | return text; 69 | } 70 | 71 | public void setText(String text) { 72 | this.text = text; 73 | } 74 | 75 | public String getSource() { 76 | return source; 77 | } 78 | 79 | public void setSource(String source) { 80 | this.source = source; 81 | } 82 | 83 | public boolean isFavorited() { 84 | return favorited; 85 | } 86 | 87 | public void setFavorited(boolean favorited) { 88 | this.favorited = favorited; 89 | } 90 | 91 | public boolean isTruncated() { 92 | return truncated; 93 | } 94 | 95 | public void setTruncated(boolean truncated) { 96 | this.truncated = truncated; 97 | } 98 | 99 | public String getInReplyToStatusId() { 100 | return inReplyToStatusId; 101 | } 102 | 103 | public void setInReplyToStatusId(String inReplyToStatusId) { 104 | this.inReplyToStatusId = inReplyToStatusId; 105 | } 106 | 107 | public String getInReplyToUserId() { 108 | return inReplyToUserId; 109 | } 110 | 111 | public void setInReplyToUserId(String inReplyToUserId) { 112 | this.inReplyToUserId = inReplyToUserId; 113 | } 114 | 115 | public String getInReplyToScreenName() { 116 | return inReplyToScreenName; 117 | } 118 | 119 | public void setInReplyToScreenName(String inReplyToScreenName) { 120 | this.inReplyToScreenName = inReplyToScreenName; 121 | } 122 | 123 | public String getMid() { 124 | return mid; 125 | } 126 | 127 | public void setMid(String mid) { 128 | this.mid = mid; 129 | } 130 | 131 | public int getRepostsCount() { 132 | return repostsCount; 133 | } 134 | 135 | public void setRepostsCount(int repostsCount) { 136 | this.repostsCount = repostsCount; 137 | } 138 | 139 | public int getCommentsCount() { 140 | return commentsCount; 141 | } 142 | 143 | public void setCommentsCount(int commentsCount) { 144 | this.commentsCount = commentsCount; 145 | } 146 | 147 | public WeiboProfile getUser() { 148 | return user; 149 | } 150 | 151 | public void setUser(WeiboProfile user) { 152 | this.user = user; 153 | } 154 | 155 | public Status getOriginalStatus() { 156 | return originalStatus; 157 | } 158 | 159 | public void setOriginalStatus(Status repost) { 160 | this.originalStatus = repost; 161 | } 162 | 163 | } 164 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/impl/AbstractWeiboOperations.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api.impl; 17 | 18 | import java.io.IOException; 19 | import java.net.URI; 20 | import java.util.List; 21 | 22 | import org.codehaus.jackson.JsonNode; 23 | import org.codehaus.jackson.map.ObjectMapper; 24 | import org.codehaus.jackson.map.type.CollectionType; 25 | import org.codehaus.jackson.map.type.TypeFactory; 26 | import org.springframework.social.MissingAuthorizationException; 27 | import org.springframework.social.UncategorizedApiException; 28 | import org.springframework.social.support.URIBuilder; 29 | import org.springframework.social.weibo.api.CursoredList; 30 | import org.springframework.util.LinkedMultiValueMap; 31 | import org.springframework.util.MultiValueMap; 32 | import org.springframework.web.client.RestTemplate; 33 | 34 | abstract class AbstractWeiboOperations { 35 | 36 | private static final String API_URL_BASE = "https://api.weibo.com/2/"; 37 | // private static final String API_URL_BASE = "http://localhost:9999/2/"; 38 | private static final LinkedMultiValueMap EMPTY_PARAMETERS = new LinkedMultiValueMap(); 39 | 40 | private final boolean isAuthorized; 41 | protected final RestTemplate restTemplate; 42 | private final ObjectMapper objectMapper; 43 | 44 | protected AbstractWeiboOperations(ObjectMapper objectMapper, 45 | RestTemplate restTemplate, boolean isAuthorized) { 46 | this.objectMapper = objectMapper; 47 | this.isAuthorized = isAuthorized; 48 | this.restTemplate = restTemplate; 49 | } 50 | 51 | protected void requireAuthorization() { 52 | if (!isAuthorized) { 53 | throw new MissingAuthorizationException(); 54 | } 55 | } 56 | 57 | protected URI buildUri(String path) { 58 | return buildUri(path, EMPTY_PARAMETERS); 59 | } 60 | 61 | protected URI buildUri(String path, String parameterName, 62 | Object parameterValue) { 63 | MultiValueMap parameters = new LinkedMultiValueMap(); 64 | parameters.set(parameterName, parameterValue.toString()); 65 | return buildUri(path, parameters); 66 | } 67 | 68 | protected URI buildUri(String path, MultiValueMap parameters) { 69 | return URIBuilder.fromUri(API_URL_BASE + path).queryParams(parameters) 70 | .build(); 71 | } 72 | 73 | protected URIBuilder uriBuilder(String path) { 74 | return URIBuilder.fromUri(API_URL_BASE + path); 75 | } 76 | 77 | protected CursoredList deserializeCursoredList(JsonNode jsonNode, 78 | final Class elementType, String dataFieldName) { 79 | CursoredList result = new CursoredList(); 80 | JsonNode previousCursorNode = jsonNode.get("previous_cursor"); 81 | if (previousCursorNode != null) { 82 | result.setPreviousCursor(previousCursorNode.getLongValue()); 83 | } 84 | JsonNode nextCursorNode = jsonNode.get("next_cursor"); 85 | if (nextCursorNode != null) { 86 | result.setNextCursor(nextCursorNode.getLongValue()); 87 | } 88 | result.setTotalNumber(jsonNode.get("total_number").getIntValue()); 89 | result.addAll(deserializeDataList(jsonNode.get(dataFieldName), 90 | elementType)); 91 | return result; 92 | } 93 | 94 | @SuppressWarnings("unchecked") 95 | protected List deserializeDataList(JsonNode jsonNode, 96 | final Class elementType) { 97 | try { 98 | CollectionType listType = TypeFactory.defaultInstance() 99 | .constructCollectionType(List.class, elementType); 100 | return (List) objectMapper.readValue(jsonNode, listType); 101 | } catch (IOException e) { 102 | throw new UncategorizedApiException( 103 | "Error deserializing data from Weibo: " + e.getMessage(), e); 104 | } 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/impl/WeiboErrorHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api.impl; 17 | 18 | import java.io.IOException; 19 | import java.util.Map; 20 | 21 | import org.codehaus.jackson.JsonFactory; 22 | import org.codehaus.jackson.JsonParseException; 23 | import org.codehaus.jackson.map.ObjectMapper; 24 | import org.codehaus.jackson.type.TypeReference; 25 | import org.springframework.http.HttpStatus; 26 | import org.springframework.http.HttpStatus.Series; 27 | import org.springframework.http.client.ClientHttpResponse; 28 | import org.springframework.social.ExpiredAuthorizationException; 29 | import org.springframework.social.InternalServerErrorException; 30 | import org.springframework.social.ServerDownException; 31 | import org.springframework.social.ServerOverloadedException; 32 | import org.springframework.social.UncategorizedApiException; 33 | import org.springframework.web.client.DefaultResponseErrorHandler; 34 | import org.springframework.web.client.RestClientException; 35 | 36 | public class WeiboErrorHandler extends DefaultResponseErrorHandler { 37 | 38 | @Override 39 | public void handleError(ClientHttpResponse response) throws IOException { 40 | HttpStatus statusCode = response.getStatusCode(); 41 | if (statusCode.series() == Series.SERVER_ERROR) { 42 | handleServerErrors(statusCode); 43 | } else if (statusCode.series() == Series.CLIENT_ERROR) { 44 | handleClientErrors(response); 45 | } 46 | 47 | // if not otherwise handled, do default handling and wrap with 48 | // UncategorizedApiException 49 | try { 50 | super.handleError(response); 51 | } catch (Exception e) { 52 | throw new UncategorizedApiException( 53 | "Error consuming Weibo REST API", e); 54 | } 55 | } 56 | 57 | /** 58 | * According to error code, throw exception 59 | * 60 | * @param response 61 | * response from Weibo server 62 | * @throws IOException 63 | */ 64 | private void handleClientErrors(ClientHttpResponse response) 65 | throws IOException { 66 | Map errorMap = extractErrorDetailsFromResponse(response); 67 | if (errorMap == null) { 68 | return; // unexpected error body, can't be handled here 69 | } 70 | 71 | int errorCode; 72 | if (errorMap.containsKey("error_code")) { 73 | errorCode = (Integer) errorMap.get("error_code"); 74 | switch (errorCode) { 75 | case 21327: 76 | throw new ExpiredAuthorizationException(); 77 | default: 78 | throw new UncategorizedApiException(String.format( 79 | "Error calling weibo: error code=%d, error message=%s", 80 | errorCode, errorMap.get("error")), 81 | new RestClientException("Unknown status code [" 82 | + response.getStatusCode() + "]")); 83 | } 84 | } 85 | } 86 | 87 | private void handleServerErrors(HttpStatus statusCode) throws IOException { 88 | if (statusCode == HttpStatus.INTERNAL_SERVER_ERROR) { 89 | throw new InternalServerErrorException( 90 | "Something is broken at Weibo."); 91 | } else if (statusCode == HttpStatus.BAD_GATEWAY) { 92 | throw new ServerDownException("Weibo is down or is being upgraded."); 93 | } else if (statusCode == HttpStatus.SERVICE_UNAVAILABLE) { 94 | throw new ServerOverloadedException( 95 | "Weibo is overloaded with requests. Try again later."); 96 | } 97 | } 98 | 99 | private Map extractErrorDetailsFromResponse( 100 | ClientHttpResponse response) throws IOException { 101 | ObjectMapper mapper = new ObjectMapper(new JsonFactory()); 102 | try { 103 | return mapper.> readValue(response.getBody(), 104 | new TypeReference>() { 105 | }); 106 | } catch (JsonParseException e) { 107 | return null; 108 | } 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/social/test/client/ResponseCreators.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.test.client; 17 | 18 | import java.io.BufferedReader; 19 | import java.io.IOException; 20 | import java.io.InputStreamReader; 21 | 22 | import org.springframework.core.io.Resource; 23 | import org.springframework.http.HttpHeaders; 24 | import org.springframework.http.HttpStatus; 25 | import org.springframework.http.client.ClientHttpRequest; 26 | import org.springframework.util.Assert; 27 | 28 | /** 29 | * Factory methods for {@link ResponseCreator} classes. Typically used to 30 | * provide input for {@link ResponseActions#andRespond(ResponseCreator)}. 31 | * 32 | * @author Arjen Poutsma 33 | * @author Craig Walls 34 | */ 35 | public abstract class ResponseCreators { 36 | private ResponseCreators() { 37 | } 38 | 39 | /** 40 | * Respond with a given response body, headers, status code, and status 41 | * text. 42 | * 43 | * @param responseBody 44 | * the body of the response 45 | * @param headers 46 | * the response headers 47 | * @param statusCode 48 | * the response status code 49 | * @param statusText 50 | * the response status text 51 | * @return a {@link ResponseCreator} 52 | */ 53 | public static ResponseCreator withResponse(final String responseBody, 54 | final HttpHeaders headers, final HttpStatus statusCode, 55 | final String statusText) { 56 | Assert.notNull(responseBody, "'responseBody' must not be null"); 57 | return new ResponseCreator() { 58 | @Override 59 | public MockClientHttpResponse createResponse( 60 | ClientHttpRequest request) { 61 | return new MockClientHttpResponse(responseBody, headers, 62 | statusCode, statusText); 63 | } 64 | }; 65 | } 66 | 67 | /** 68 | * Response with a given response body and headers. The response status code 69 | * is HTTP 200 (OK). 70 | * 71 | * @param responseBody 72 | * the body of the response 73 | * @param headers 74 | * the response headers 75 | * @return a {@link ResponseCreator} 76 | */ 77 | public static ResponseCreator withResponse(String responseBody, 78 | HttpHeaders headers) { 79 | return withResponse(responseBody, headers, HttpStatus.OK, ""); 80 | } 81 | 82 | /** 83 | * Respond with a given response body (from a {@link Resource}) and headers. 84 | * The response status code is HTTP 200 (OK). 85 | * 86 | * @param responseBodyResource 87 | * a {@link Resource} containing the body of the response 88 | * @param headers 89 | * the response headers 90 | * @param statusCode 91 | * the response status code 92 | * @param statusText 93 | * the response status text 94 | * @return a {@link ResponseCreator} 95 | */ 96 | public static ResponseCreator withResponse( 97 | final Resource responseBodyResource, final HttpHeaders headers, 98 | final HttpStatus statusCode, final String statusText) { 99 | return withResponse(readResource(responseBodyResource), headers, 100 | statusCode, statusText); 101 | } 102 | 103 | /** 104 | * Response with a given response body and headers. The response status code 105 | * is HTTP 200 (OK). 106 | * 107 | * @param responseBody 108 | * the body of the response 109 | * @param headers 110 | * the response headers 111 | * @return a {@link ResponseCreator} 112 | */ 113 | public static ResponseCreator withResponse(Resource responseBody, 114 | HttpHeaders headers) { 115 | return withResponse(responseBody, headers, HttpStatus.OK, ""); 116 | } 117 | 118 | private static String readResource(Resource resource) { 119 | StringBuilder resourceText = new StringBuilder(); 120 | try { 121 | BufferedReader reader = new BufferedReader(new InputStreamReader( 122 | resource.getInputStream(), "UTF-8")); 123 | while (reader.ready()) { 124 | resourceText.append(reader.readLine() + "\n"); 125 | } 126 | } catch (IOException e) { 127 | } 128 | return resourceText.toString(); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/TimelineOperations.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api; 17 | 18 | import java.util.List; 19 | 20 | import org.springframework.core.io.Resource; 21 | 22 | public interface TimelineOperations { 23 | 24 | Status deleteStatus(long id); 25 | 26 | CursoredList getBilateralTimeline(); 27 | 28 | CursoredList getBilateralTimeline(int pageSize, int pageNumber); 29 | 30 | CursoredList getBilateralTimeline(int pageSize, int pageNumber, 31 | boolean onlyApplicationStatus); 32 | 33 | CursoredList getBilateralTimeline(long sinceId, long maxId, 34 | int pageSize, int pageNumber, boolean onlyApplicationStatus, 35 | StatusContentType statusContentType); 36 | 37 | List getDailyHotComments(); 38 | 39 | List getDailyHotComments(int pageSize, boolean onlyApplicationStatus); 40 | 41 | List getDailyHotRepost(); 42 | 43 | List getDailyHotRepost(int pageSize, boolean onlyApplicationStatus); 44 | 45 | CursoredList getFriendsTimeline(); 46 | 47 | CursoredList getFriendsTimeline(int pageSize, int pageNumber); 48 | 49 | CursoredList getFriendsTimeline(int pageSize, int pageNumber, 50 | boolean onlyApplicationStatus); 51 | 52 | CursoredList getFriendsTimeline(long sinceId, long maxId, 53 | int pageSize, int pageNumber, boolean onlyApplicationStatus, 54 | StatusContentType statusContentType); 55 | 56 | CursoredList getHomeTimeline(); 57 | 58 | CursoredList getHomeTimeline(int pageSize, int pageNumber); 59 | 60 | CursoredList getHomeTimeline(int pageSize, int pageNumber, 61 | boolean onlyApplicationStatus); 62 | 63 | CursoredList getHomeTimeline(long sinceId, long maxId, 64 | int pageSize, int pageNumber, boolean onlyApplicationStatus, 65 | StatusContentType statusContentType); 66 | 67 | CursoredList getMentions(); 68 | 69 | CursoredList getMentions(int pageSize, int pageNumber); 70 | 71 | CursoredList getMentions(long sinceId, long maxId, int pageSize, 72 | int pageNumber, AuthorFilterType authorFilterType, 73 | SourceFilterType sourceFilterType, boolean createdInWeibo); 74 | 75 | CursoredList getPublicTimeline(); 76 | 77 | CursoredList getPublicTimeline(int pageSize, int pageNumber); 78 | 79 | CursoredList getPublicTimeline(int pageSize, int pageNumber, 80 | boolean onlyApplicationStatus); 81 | 82 | CursoredList getRepostByMe(); 83 | 84 | CursoredList getRepostByMe(int pageSize, int pageNumber); 85 | 86 | CursoredList getRepostByMe(long sinceId, long maxId, int pageSize, 87 | int pageNumber); 88 | 89 | CursoredList getRepostTimeline(long id); 90 | 91 | CursoredList getRepostTimeline(long id, int pageSize, int pageNumber); 92 | 93 | CursoredList getRepostTimeline(long id, long sinceId, long maxId, 94 | int pageSize, int pageNumber, AuthorFilterType authorFilterType); 95 | 96 | Status getStatus(long id); 97 | 98 | CursoredList getUserTimeline(long uid); 99 | 100 | CursoredList getUserTimeline(long uid, int pageSize, int pageNumber); 101 | 102 | CursoredList getUserTimeline(long uid, int pageSize, 103 | int pageNumber, boolean onlyApplicationStatus); 104 | 105 | CursoredList getUserTimeline(long uid, long sinceId, long maxId, 106 | int pageSize, int pageNumber, boolean onlyApplicationStatus, 107 | StatusContentType statusContentType); 108 | 109 | List getWeeklyHotComments(); 110 | 111 | List getWeeklyHotComments(int pageSize, 112 | boolean onlyApplicationStatus); 113 | 114 | List getWeeklyHotRepost(); 115 | 116 | List getWeeklyHotRepost(int pageSize, boolean onlyApplicationStatus); 117 | 118 | Status repostStatus(long id, String message); 119 | 120 | Status updateStatus(String message); 121 | 122 | Status updateStatus(String message, float latitude, float longitude); 123 | 124 | Status updateStatus(String message, Resource media); 125 | 126 | Status updateStatus(String message, Resource media, float latitude, 127 | float longitude); 128 | 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/impl/TrendTemplate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api.impl; 17 | 18 | import java.util.List; 19 | 20 | import org.codehaus.jackson.JsonNode; 21 | import org.codehaus.jackson.map.ObjectMapper; 22 | import org.springframework.social.weibo.api.FollowedTrend; 23 | import org.springframework.social.weibo.api.TrendOperations; 24 | import org.springframework.social.weibo.api.TrendsWrapper; 25 | import org.springframework.social.weibo.api.UserTrend; 26 | import org.springframework.social.weibo.util.StringUtils; 27 | import org.springframework.util.LinkedMultiValueMap; 28 | import org.springframework.util.MultiValueMap; 29 | import org.springframework.web.client.RestTemplate; 30 | 31 | public class TrendTemplate extends AbstractWeiboOperations implements 32 | TrendOperations { 33 | 34 | protected TrendTemplate(ObjectMapper objectMapper, 35 | RestTemplate restTemplate, boolean isAuthorized) { 36 | super(objectMapper, restTemplate, isAuthorized); 37 | } 38 | 39 | @Override 40 | public List getTrends(long userId) { 41 | requireAuthorization(); 42 | JsonNode jsonNode = restTemplate.getForObject( 43 | buildUri("trends.json", "uid", String.valueOf(userId)), 44 | JsonNode.class); 45 | return deserializeDataList(jsonNode, UserTrend.class); 46 | } 47 | 48 | @Override 49 | public List getTrends(long userId, int pageSize, int pageNumber) { 50 | requireAuthorization(); 51 | JsonNode jsonNode = restTemplate 52 | .getForObject( 53 | uriBuilder("trends.json") 54 | .queryParam("uid", String.valueOf(userId)) 55 | .queryParam("count", String.valueOf(pageSize)) 56 | .queryParam("page", String.valueOf(pageNumber)) 57 | .build(), JsonNode.class); 58 | return deserializeDataList(jsonNode, UserTrend.class); 59 | } 60 | 61 | @Override 62 | public FollowedTrend isFollowed(String trendName) { 63 | requireAuthorization(); 64 | return restTemplate.getForObject( 65 | buildUri("trends/is_follow.json", "trend_name", trendName), 66 | FollowedTrend.class); 67 | } 68 | 69 | @Override 70 | public TrendsWrapper getHourlyTrends() { 71 | requireAuthorization(); 72 | return restTemplate.getForObject(buildUri("trends/hourly.json"), 73 | TrendsWrapper.class); 74 | } 75 | 76 | @Override 77 | public TrendsWrapper getHourlyTrends(boolean onlyApplicationData) { 78 | requireAuthorization(); 79 | return restTemplate.getForObject( 80 | buildUri("trends/hourly.json", "base_app", 81 | StringUtils.booleanToString(onlyApplicationData)), 82 | TrendsWrapper.class); 83 | } 84 | 85 | @Override 86 | public TrendsWrapper getDailyTrends() { 87 | requireAuthorization(); 88 | return restTemplate.getForObject(buildUri("trends/daily.json"), 89 | TrendsWrapper.class); 90 | } 91 | 92 | @Override 93 | public TrendsWrapper getDailyTrends(boolean onlyApplicationData) { 94 | requireAuthorization(); 95 | return restTemplate.getForObject( 96 | buildUri("trends/daily.json", "base_app", 97 | StringUtils.booleanToString(onlyApplicationData)), 98 | TrendsWrapper.class); 99 | } 100 | 101 | @Override 102 | public TrendsWrapper getWeeklyTrends() { 103 | requireAuthorization(); 104 | return restTemplate.getForObject(buildUri("trends/weekly.json"), 105 | TrendsWrapper.class); 106 | } 107 | 108 | @Override 109 | public TrendsWrapper getWeeklyTrends(boolean onlyApplicationData) { 110 | requireAuthorization(); 111 | return restTemplate.getForObject( 112 | buildUri("trends/weekly.json", "base_app", 113 | StringUtils.booleanToString(onlyApplicationData)), 114 | TrendsWrapper.class); 115 | } 116 | 117 | @Override 118 | public long follow(String trendName) { 119 | requireAuthorization(); 120 | MultiValueMap request = new LinkedMultiValueMap( 121 | 1); 122 | request.add("trend_name", trendName); 123 | return restTemplate 124 | .postForObject(buildUri("trends/follow.json"), request, 125 | JsonNode.class).findValue("topicid").asLong(); 126 | } 127 | 128 | @Override 129 | public boolean unfollow(long trendId) { 130 | requireAuthorization(); 131 | MultiValueMap request = new LinkedMultiValueMap( 132 | 1); 133 | request.add("trend_id", String.valueOf(trendId)); 134 | return restTemplate 135 | .postForObject(buildUri("trends/destroy.json"), request, 136 | JsonNode.class).findValue("result").asBoolean(); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/test/resources/json/comments.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "created_at" : "Wed Jun 01 00:50:25 +0800 2011", 3 | "id" : 12438492184, 4 | "text" : "我喜欢你做的", 5 | "source" : "新浪微博", 6 | "mid" : "202110601896455629", 7 | "user" : { 8 | "id" : 1404376560, 9 | "screen_name" : "zaku", 10 | "name" : "zaku", 11 | "province" : "11", 12 | "city" : "5", 13 | "location" : "北京 朝阳区", 14 | "description" : "人生五十年,乃如梦如幻;有生斯有死,壮士复何憾。", 15 | "url" : "http://blog.sina.com.cn/zaku", 16 | "profile_image_url" : "http://tp1.sinaimg.cn/1404376560/50/0/1", 17 | "domain" : "zaku", 18 | "gender" : "m", 19 | "followers_count" : 1204, 20 | "friends_count" : 447, 21 | "statuses_count" : 2908, 22 | "favourites_count" : 0, 23 | "created_at" : "Fri Aug 28 00:00:00 +0800 2009", 24 | "following" : false, 25 | "allow_all_act_msg" : false, 26 | "remark" : "", 27 | "geo_enabled" : true, 28 | "verified" : false, 29 | "allow_all_comment" : true, 30 | "avatar_large" : "http://tp1.sinaimg.cn/1404376560/180/0/1", 31 | "verified_reason" : "", 32 | "follow_me" : false, 33 | "online_status" : 0, 34 | "bi_followers_count" : 215 35 | }, 36 | "status" : { 37 | "created_at" : "Tue May 31 17:46:55 +0800 2011", 38 | "id" : 11488058246, 39 | "text" : "求关注。", 40 | "source" : "新浪微博", 41 | "favorited" : false, 42 | "truncated" : false, 43 | "in_reply_to_status_id" : "", 44 | "in_reply_to_user_id" : "", 45 | "in_reply_to_screen_name" : "", 46 | "geo" : null, 47 | "mid" : "5612814510546515491", 48 | "reposts_count" : 8, 49 | "comments_count" : 9, 50 | "annotations" : [], 51 | "user" : { 52 | "id" : 1404376560, 53 | "screen_name" : "zaku", 54 | "name" : "zaku", 55 | "province" : "11", 56 | "city" : "5", 57 | "location" : "北京 朝阳区", 58 | "description" : "人生五十年,乃如梦如幻;有生斯有死,壮士复何憾。", 59 | "url" : "http://blog.sina.com.cn/zaku", 60 | "profile_image_url" : "http://tp1.sinaimg.cn/1404376560/50/0/1", 61 | "domain" : "zaku", 62 | "gender" : "m", 63 | "followers_count" : 1204, 64 | "friends_count" : 447, 65 | "statuses_count" : 2908, 66 | "favourites_count" : 0, 67 | "created_at" : "Fri Aug 28 00:00:00 +0800 2009", 68 | "following" : false, 69 | "allow_all_act_msg" : false, 70 | "remark" : "", 71 | "geo_enabled" : true, 72 | "verified" : false, 73 | "allow_all_comment" : true, 74 | "avatar_large" : "http://tp1.sinaimg.cn/1404376560/180/0/1", 75 | "verified_reason" : "", 76 | "follow_me" : false, 77 | "online_status" : 0, 78 | "bi_followers_count" : 215 79 | } 80 | } 81 | }, { 82 | "created_at" : "Wed Jun 01 00:00:25 +0800 2011", 83 | "id" : 12438492183, 84 | "text" : "not bad", 85 | "source" : "新浪微博", 86 | "mid" : "202110601896455629", 87 | "user" : { 88 | "id" : 1404376569, 89 | "screen_name" : "John Rambo", 90 | "name" : "john", 91 | "province" : "11", 92 | "city" : "5", 93 | "location" : "北京 朝阳区", 94 | "description" : "人生五十年,乃如梦如幻;有生斯有死,壮士复何憾。", 95 | "url" : "http://blog.sina.com.cn/john", 96 | "profile_image_url" : "http://tp1.sinaimg.cn/1404376560/50/0/1", 97 | "domain" : "john", 98 | "gender" : "m", 99 | "followers_count" : 120000, 100 | "friends_count" : 12000, 101 | "statuses_count" : 50000, 102 | "favourites_count" : 0, 103 | "created_at" : "Fri Aug 28 00:00:00 +0800 2009", 104 | "following" : false, 105 | "allow_all_act_msg" : false, 106 | "remark" : "", 107 | "geo_enabled" : true, 108 | "verified" : false, 109 | "allow_all_comment" : true, 110 | "avatar_large" : "http://tp1.sinaimg.cn/1404376560/180/0/1", 111 | "verified_reason" : "", 112 | "follow_me" : false, 113 | "online_status" : 0, 114 | "bi_followers_count" : 215 115 | }, 116 | "status" : { 117 | "created_at" : "Tue May 31 17:46:55 +0800 2011", 118 | "id" : 11488058246, 119 | "text" : "求关注。", 120 | "source" : "新浪微博", 121 | "favorited" : false, 122 | "truncated" : false, 123 | "in_reply_to_status_id" : "", 124 | "in_reply_to_user_id" : "", 125 | "in_reply_to_screen_name" : "", 126 | "geo" : null, 127 | "mid" : "5612814510546515491", 128 | "reposts_count" : 8, 129 | "comments_count" : 9, 130 | "annotations" : [], 131 | "user" : { 132 | "id" : 1404376569, 133 | "screen_name" : "John Rambo", 134 | "name" : "john", 135 | "province" : "11", 136 | "city" : "5", 137 | "location" : "北京 朝阳区", 138 | "description" : "人生五十年,乃如梦如幻;有生斯有死,壮士复何憾。", 139 | "url" : "http://blog.sina.com.cn/john", 140 | "profile_image_url" : "http://tp1.sinaimg.cn/1404376560/50/0/1", 141 | "domain" : "john", 142 | "gender" : "m", 143 | "followers_count" : 1204, 144 | "friends_count" : 447, 145 | "statuses_count" : 2908, 146 | "favourites_count" : 0, 147 | "created_at" : "Fri Aug 28 00:00:00 +0800 2009", 148 | "following" : false, 149 | "allow_all_act_msg" : false, 150 | "remark" : "", 151 | "geo_enabled" : true, 152 | "verified" : false, 153 | "allow_all_comment" : true, 154 | "avatar_large" : "http://tp1.sinaimg.cn/1404376560/180/0/1", 155 | "verified_reason" : "", 156 | "follow_me" : false, 157 | "online_status" : 0, 158 | "bi_followers_count" : 215 159 | } 160 | } 161 | } 162 | ] -------------------------------------------------------------------------------- /src/test/resources/json/cursoredComments.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments" : [{ 3 | "created_at" : "Wed Jun 01 00:50:25 +0800 2011", 4 | "id" : 12438492184, 5 | "text" : "我喜欢你做的", 6 | "source" : "新浪微博", 7 | "mid" : "202110601896455629", 8 | "user" : { 9 | "id" : 1404376560, 10 | "screen_name" : "zaku", 11 | "name" : "zaku", 12 | "province" : "11", 13 | "city" : "5", 14 | "location" : "北京 朝阳区", 15 | "description" : "人生五十年,乃如梦如幻;有生斯有死,壮士复何憾。", 16 | "url" : "http://blog.sina.com.cn/zaku", 17 | "profile_image_url" : "http://tp1.sinaimg.cn/1404376560/50/0/1", 18 | "domain" : "zaku", 19 | "gender" : "m", 20 | "followers_count" : 1204, 21 | "friends_count" : 447, 22 | "statuses_count" : 2908, 23 | "favourites_count" : 0, 24 | "created_at" : "Fri Aug 28 00:00:00 +0800 2009", 25 | "following" : false, 26 | "allow_all_act_msg" : false, 27 | "remark" : "", 28 | "geo_enabled" : true, 29 | "verified" : false, 30 | "allow_all_comment" : true, 31 | "avatar_large" : "http://tp1.sinaimg.cn/1404376560/180/0/1", 32 | "verified_reason" : "", 33 | "follow_me" : false, 34 | "online_status" : 0, 35 | "bi_followers_count" : 215 36 | }, 37 | "status" : { 38 | "created_at" : "Tue May 31 17:46:55 +0800 2011", 39 | "id" : 11488058246, 40 | "text" : "求关注。", 41 | "source" : "新浪微博", 42 | "favorited" : false, 43 | "truncated" : false, 44 | "in_reply_to_status_id" : "", 45 | "in_reply_to_user_id" : "", 46 | "in_reply_to_screen_name" : "", 47 | "geo" : null, 48 | "mid" : "5612814510546515491", 49 | "reposts_count" : 8, 50 | "comments_count" : 9, 51 | "annotations" : [], 52 | "user" : { 53 | "id" : 1404376560, 54 | "screen_name" : "zaku", 55 | "name" : "zaku", 56 | "province" : "11", 57 | "city" : "5", 58 | "location" : "北京 朝阳区", 59 | "description" : "人生五十年,乃如梦如幻;有生斯有死,壮士复何憾。", 60 | "url" : "http://blog.sina.com.cn/zaku", 61 | "profile_image_url" : "http://tp1.sinaimg.cn/1404376560/50/0/1", 62 | "domain" : "zaku", 63 | "gender" : "m", 64 | "followers_count" : 1204, 65 | "friends_count" : 447, 66 | "statuses_count" : 2908, 67 | "favourites_count" : 0, 68 | "created_at" : "Fri Aug 28 00:00:00 +0800 2009", 69 | "following" : false, 70 | "allow_all_act_msg" : false, 71 | "remark" : "", 72 | "geo_enabled" : true, 73 | "verified" : false, 74 | "allow_all_comment" : true, 75 | "avatar_large" : "http://tp1.sinaimg.cn/1404376560/180/0/1", 76 | "verified_reason" : "", 77 | "follow_me" : false, 78 | "online_status" : 0, 79 | "bi_followers_count" : 215 80 | } 81 | } 82 | }, { 83 | "created_at" : "Wed Jun 01 00:00:25 +0800 2011", 84 | "id" : 12438492183, 85 | "text" : "not bad", 86 | "source" : "新浪微博", 87 | "mid" : "202110601896455629", 88 | "user" : { 89 | "id" : 1404376569, 90 | "screen_name" : "John Rambo", 91 | "name" : "john", 92 | "province" : "11", 93 | "city" : "5", 94 | "location" : "北京 朝阳区", 95 | "description" : "人生五十年,乃如梦如幻;有生斯有死,壮士复何憾。", 96 | "url" : "http://blog.sina.com.cn/john", 97 | "profile_image_url" : "http://tp1.sinaimg.cn/1404376560/50/0/1", 98 | "domain" : "john", 99 | "gender" : "m", 100 | "followers_count" : 120000, 101 | "friends_count" : 12000, 102 | "statuses_count" : 50000, 103 | "favourites_count" : 0, 104 | "created_at" : "Fri Aug 28 00:00:00 +0800 2009", 105 | "following" : false, 106 | "allow_all_act_msg" : false, 107 | "remark" : "", 108 | "geo_enabled" : true, 109 | "verified" : false, 110 | "allow_all_comment" : true, 111 | "avatar_large" : "http://tp1.sinaimg.cn/1404376560/180/0/1", 112 | "verified_reason" : "", 113 | "follow_me" : false, 114 | "online_status" : 0, 115 | "bi_followers_count" : 215 116 | }, 117 | "status" : { 118 | "created_at" : "Tue May 31 17:46:55 +0800 2011", 119 | "id" : 11488058246, 120 | "text" : "求关注。", 121 | "source" : "新浪微博", 122 | "favorited" : false, 123 | "truncated" : false, 124 | "in_reply_to_status_id" : "", 125 | "in_reply_to_user_id" : "", 126 | "in_reply_to_screen_name" : "", 127 | "geo" : null, 128 | "mid" : "5612814510546515491", 129 | "reposts_count" : 8, 130 | "comments_count" : 9, 131 | "annotations" : [], 132 | "user" : { 133 | "id" : 1404376569, 134 | "screen_name" : "John Rambo", 135 | "name" : "john", 136 | "province" : "11", 137 | "city" : "5", 138 | "location" : "北京 朝阳区", 139 | "description" : "人生五十年,乃如梦如幻;有生斯有死,壮士复何憾。", 140 | "url" : "http://blog.sina.com.cn/john", 141 | "profile_image_url" : "http://tp1.sinaimg.cn/1404376560/50/0/1", 142 | "domain" : "john", 143 | "gender" : "m", 144 | "followers_count" : 1204, 145 | "friends_count" : 447, 146 | "statuses_count" : 2908, 147 | "favourites_count" : 0, 148 | "created_at" : "Fri Aug 28 00:00:00 +0800 2009", 149 | "following" : false, 150 | "allow_all_act_msg" : false, 151 | "remark" : "", 152 | "geo_enabled" : true, 153 | "verified" : false, 154 | "allow_all_comment" : true, 155 | "avatar_large" : "http://tp1.sinaimg.cn/1404376560/180/0/1", 156 | "verified_reason" : "", 157 | "follow_me" : false, 158 | "online_status" : 0, 159 | "bi_followers_count" : 215 160 | } 161 | } 162 | } 163 | ], 164 | "previous_cursor" : 0, 165 | "next_cursor" : 10, 166 | "total_number" : 7 167 | } 168 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/impl/FavoriteTemplate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api.impl; 17 | 18 | import java.util.List; 19 | 20 | import org.codehaus.jackson.JsonNode; 21 | import org.codehaus.jackson.map.ObjectMapper; 22 | import org.springframework.social.weibo.api.CursoredList; 23 | import org.springframework.social.weibo.api.Favorite; 24 | import org.springframework.social.weibo.api.Favorite.Tag; 25 | import org.springframework.social.weibo.api.FavoriteOperations; 26 | import org.springframework.social.weibo.util.StringUtils; 27 | import org.springframework.util.LinkedMultiValueMap; 28 | import org.springframework.util.MultiValueMap; 29 | import org.springframework.web.client.RestTemplate; 30 | 31 | public class FavoriteTemplate extends AbstractWeiboOperations implements 32 | FavoriteOperations { 33 | 34 | protected FavoriteTemplate(ObjectMapper objectMapper, 35 | RestTemplate restTemplate, boolean isAuthorized) { 36 | super(objectMapper, restTemplate, isAuthorized); 37 | } 38 | 39 | @Override 40 | public Favorite createFavorite(long id) { 41 | requireAuthorization(); 42 | MultiValueMap request = new LinkedMultiValueMap( 43 | 1); 44 | request.add("id", String.valueOf(id)); 45 | return restTemplate.postForObject(buildUri("favorites/create.json"), 46 | request, Favorite.class); 47 | } 48 | 49 | @Override 50 | public Favorite deleteFavorite(long id) { 51 | requireAuthorization(); 52 | MultiValueMap request = new LinkedMultiValueMap( 53 | 1); 54 | request.add("id", String.valueOf(id)); 55 | return restTemplate.postForObject(buildUri("favorites/destroy.json"), 56 | request, Favorite.class); 57 | } 58 | 59 | @Override 60 | public boolean deleteFavorites(List ids) { 61 | requireAuthorization(); 62 | MultiValueMap request = new LinkedMultiValueMap( 63 | 1); 64 | request.add("ids", StringUtils.join(ids)); 65 | JsonNode jsonNode = restTemplate.postForObject( 66 | buildUri("favorites/destroy_batch.json"), request, 67 | JsonNode.class); 68 | return jsonNode.get("result").asBoolean(); 69 | } 70 | 71 | @Override 72 | public Favorite getFavorite(long id) { 73 | requireAuthorization(); 74 | return restTemplate.getForObject(uriBuilder("favorites/show.json") 75 | .queryParam("id", String.valueOf(id)).build(), Favorite.class); 76 | } 77 | 78 | @Override 79 | public CursoredList getFavorites() { 80 | requireAuthorization(); 81 | JsonNode jsonNode = restTemplate.getForObject( 82 | buildUri("favorites.json"), JsonNode.class); 83 | return deserializeCursoredList(jsonNode, Favorite.class, "favorites"); 84 | } 85 | 86 | @Override 87 | public CursoredList getFavorites(int pageSize, int pageNumber) { 88 | requireAuthorization(); 89 | JsonNode jsonNode = restTemplate 90 | .getForObject( 91 | uriBuilder("favorites.json") 92 | .queryParam("count", String.valueOf(pageSize)) 93 | .queryParam("page", String.valueOf(pageNumber)) 94 | .build(), JsonNode.class); 95 | return deserializeCursoredList(jsonNode, Favorite.class, "favorites"); 96 | } 97 | 98 | @Override 99 | public CursoredList getFavoritesByTag(long tagId) { 100 | requireAuthorization(); 101 | JsonNode jsonNode = restTemplate.getForObject( 102 | uriBuilder("favorites/by_tags.json").queryParam("tid", 103 | String.valueOf(tagId)).build(), JsonNode.class); 104 | return deserializeCursoredList(jsonNode, Favorite.class, "favorites"); 105 | } 106 | 107 | @Override 108 | public CursoredList getFavoritesByTag(long tagId, int pageSize, 109 | int pageNumber) { 110 | requireAuthorization(); 111 | JsonNode jsonNode = restTemplate 112 | .getForObject( 113 | uriBuilder("favorites/by_tags.json") 114 | .queryParam("tid", String.valueOf(tagId)) 115 | .queryParam("count", String.valueOf(pageSize)) 116 | .queryParam("page", String.valueOf(pageNumber)) 117 | .build(), JsonNode.class); 118 | return deserializeCursoredList(jsonNode, Favorite.class, "favorites"); 119 | } 120 | 121 | @Override 122 | public CursoredList getTags() { 123 | requireAuthorization(); 124 | JsonNode jsonNode = restTemplate.getForObject( 125 | buildUri("favorites/tags.json"), JsonNode.class); 126 | return deserializeCursoredList(jsonNode, Tag.class, "tags"); 127 | } 128 | 129 | @Override 130 | public CursoredList getTags(int pageSize, int pageNumber) { 131 | requireAuthorization(); 132 | JsonNode jsonNode = restTemplate 133 | .getForObject( 134 | uriBuilder("favorites/tags.json") 135 | .queryParam("count", String.valueOf(pageSize)) 136 | .queryParam("page", String.valueOf(pageNumber)) 137 | .build(), JsonNode.class); 138 | return deserializeCursoredList(jsonNode, Tag.class, "tags"); 139 | } 140 | 141 | @Override 142 | public Favorite updateTags(long id, List tags) { 143 | requireAuthorization(); 144 | MultiValueMap request = new LinkedMultiValueMap( 145 | 2); 146 | request.add("id", String.valueOf(id)); 147 | request.add("tags", StringUtils.join(tags)); 148 | return restTemplate 149 | .postForObject(buildUri("favorites/tags/update.json"), request, 150 | Favorite.class); 151 | } 152 | 153 | } 154 | -------------------------------------------------------------------------------- /src/test/resources/json/hotStatuses.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "created_at" : "Tue May 31 17:46:55 +0800 2011", 3 | "id" : 11488058246, 4 | "text" : "你好", 5 | "source" : "Sina microblogging", 6 | "favorited" : false, 7 | "truncated" : false, 8 | "in_reply_to_status_id" : "", 9 | "in_reply_to_user_id" : "", 10 | "in_reply_to_screen_name" : "", 11 | "mid" : "5612814510546515491", 12 | "reposts_count" : 8, 13 | "comments_count" : 9, 14 | "geo" : null, 15 | "user" : { 16 | "id" : 1404376560, 17 | "screen_name" : "zaku", 18 | "name" : "zaku", 19 | "province" : "11", 20 | "city" : "5", 21 | "location" : "Beijing's Chaoyang District.", 22 | "description" : "Fifty years of life, is dreamlike; give birth, then you have died, warrior What else regret.", 23 | "url" : "http://blog.sina.com.cn/zaku", 24 | "profile_image_url" : "http://tp1.sinaimg.cn/1404376560/50/0/1", 25 | "domain" : "zaku", 26 | "gender" : "m", 27 | "followers_count" : 1204, 28 | "friends_count" : 447, 29 | "statuses_count" : 2908, 30 | "favourites_count" : 0, 31 | "created_at" : "Fri Aug 28 00:00:00 +0800 2009", 32 | "following" : false, 33 | "allow_all_act_msg" : false, 34 | "remark" : "", 35 | "geo_enabled" : true, 36 | "verified" : false, 37 | "allow_all_comment" : true, 38 | "avatar_large" : "http://tp1.sinaimg.cn/1404376560/180/0/1", 39 | "verified_reason" : "", 40 | "follow_me" : false, 41 | "online_status" : 0, 42 | "bi_followers_count" : 215 43 | }, 44 | "retweeted_status" : { 45 | "created_at" : "Tue May 24 18:04:53 +0800 2011", 46 | "id" : 11142488790, 47 | "text" : "你好", 48 | "source" : "新浪微博", 49 | "favorited" : false, 50 | "truncated" : false, 51 | "in_reply_to_status_id" : "", 52 | "in_reply_to_user_id" : "", 53 | "in_reply_to_screen_name" : "", 54 | "geo" : null, 55 | "mid" : "5610221544300749636", 56 | "annotations" : [], 57 | "reposts_count" : 5, 58 | "comments_count" : 8, 59 | "user" : { 60 | "id" : 1073880650, 61 | "screen_name" : "檀木幻想", 62 | "name" : "檀木幻想", 63 | "province" : "11", 64 | "city" : "5", 65 | "location" : "北京 朝阳区", 66 | "description" : "请访问微博分析家。", 67 | "url" : "http://www.weibo007.com/", 68 | "profile_image_url" : "http://tp3.sinaimg.cn/1073880650/50/1285051202/1", 69 | "domain" : "woodfantasy", 70 | "gender" : "m", 71 | "followers_count" : 723, 72 | "friends_count" : 415, 73 | "statuses_count" : 587, 74 | "favourites_count" : 107, 75 | "created_at" : "Sat Nov 14 00:00:00 +0800 2009", 76 | "following" : true, 77 | "allow_all_act_msg" : true, 78 | "remark" : "", 79 | "geo_enabled" : true, 80 | "verified" : false, 81 | "allow_all_comment" : true, 82 | "avatar_large" : "http://tp3.sinaimg.cn/1073880650/180/1285051202/1", 83 | "verified_reason" : "", 84 | "follow_me" : true, 85 | "online_status" : 0, 86 | "bi_followers_count" : 199 87 | } 88 | } 89 | }, { 90 | "created_at" : "Tue May 31 17:56:55 +0800 2011", 91 | "id" : 11488058246, 92 | "text" : "seeking attention.", 93 | "source" : "Sina microblogging", 94 | "favorited" : false, 95 | "truncated" : false, 96 | "in_reply_to_status_id" : "", 97 | "in_reply_to_user_id" : "", 98 | "in_reply_to_screen_name" : "", 99 | "geo" : null, 100 | "mid" : "5612814510546515491", 101 | "reposts_count" : 8, 102 | "comments_count" : 9, 103 | "annotations" : [{ 104 | "server_ip" : "10.75.15.190" 105 | } 106 | ], 107 | "geo" : { 108 | "type" : "Point", 109 | "coordinates" : [30.722584, 104.033936] 110 | }, 111 | "user" : { 112 | "id" : 1404376560, 113 | "screen_name" : "zaku", 114 | "name" : "zaku", 115 | "province" : "11", 116 | "city" : "5", 117 | "location" : "Beijing's Chaoyang District.", 118 | "description" : "Fifty years of life, is dreamlike; give birth, then you have died, warrior What else regret.", 119 | "url" : "http://blog.sina.com.cn/zaku", 120 | "profile_image_url" : "http://tp1.sinaimg.cn/1404376560/50/0/1", 121 | "domain" : "zaku", 122 | "gender" : "m", 123 | "followers_count" : 1204, 124 | "friends_count" : 447, 125 | "statuses_count" : 2908, 126 | "favourites_count" : 0, 127 | "created_at" : "Fri Aug 28 00:00:00 +0800 2009", 128 | "following" : false, 129 | "allow_all_act_msg" : false, 130 | "remark" : "", 131 | "geo_enabled" : true, 132 | "verified" : false, 133 | "allow_all_comment" : true, 134 | "avatar_large" : "http://tp1.sinaimg.cn/1404376560/180/0/1", 135 | "verified_reason" : "", 136 | "follow_me" : false, 137 | "online_status" : 0, 138 | "bi_followers_count" : 215 139 | }, 140 | "retweeted_status" : { 141 | "created_at" : "Tue May 24 18:04:53 +0800 2011", 142 | "id" : 11142488790, 143 | "text" : "你好", 144 | "source" : "新浪微博", 145 | "favorited" : false, 146 | "truncated" : false, 147 | "in_reply_to_status_id" : "", 148 | "in_reply_to_user_id" : "", 149 | "in_reply_to_screen_name" : "", 150 | "geo" : null, 151 | "mid" : "5610221544300749636", 152 | "annotations" : [], 153 | "reposts_count" : 5, 154 | "comments_count" : 8, 155 | "user" : { 156 | "id" : 1073880650, 157 | "screen_name" : "檀木幻想", 158 | "name" : "檀木幻想", 159 | "province" : "11", 160 | "city" : "5", 161 | "location" : "北京 朝阳区", 162 | "description" : "请访问微博分析家。", 163 | "url" : "http://www.weibo007.com/", 164 | "profile_image_url" : "http://tp3.sinaimg.cn/1073880650/50/1285051202/1", 165 | "domain" : "woodfantasy", 166 | "gender" : "m", 167 | "followers_count" : 723, 168 | "friends_count" : 415, 169 | "statuses_count" : 587, 170 | "favourites_count" : 107, 171 | "created_at" : "Sat Nov 14 00:00:00 +0800 2009", 172 | "following" : true, 173 | "allow_all_act_msg" : true, 174 | "remark" : "", 175 | "geo_enabled" : true, 176 | "verified" : false, 177 | "allow_all_comment" : true, 178 | "avatar_large" : "http://tp3.sinaimg.cn/1073880650/180/1285051202/1", 179 | "verified_reason" : "", 180 | "follow_me" : true, 181 | "online_status" : 0, 182 | "bi_followers_count" : 199 183 | } 184 | } 185 | } 186 | ] -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/impl/FriendTemplate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api.impl; 17 | 18 | import java.util.List; 19 | 20 | import org.codehaus.jackson.JsonNode; 21 | import org.codehaus.jackson.map.ObjectMapper; 22 | import org.springframework.social.weibo.api.CursoredList; 23 | import org.springframework.social.weibo.api.FriendOperations; 24 | import org.springframework.social.weibo.api.WeiboProfile; 25 | import org.springframework.util.LinkedMultiValueMap; 26 | import org.springframework.util.MultiValueMap; 27 | import org.springframework.web.client.RestTemplate; 28 | 29 | class FriendTemplate extends AbstractWeiboOperations implements 30 | FriendOperations { 31 | 32 | protected FriendTemplate(ObjectMapper objectMapper, 33 | RestTemplate restTemplate, boolean isAuthorized) { 34 | super(objectMapper, restTemplate, isAuthorized); 35 | } 36 | 37 | @Override 38 | public WeiboProfile createFriend(long uid) { 39 | requireAuthorization(); 40 | MultiValueMap request = new LinkedMultiValueMap( 41 | 1); 42 | request.add("uid", String.valueOf(uid)); 43 | return restTemplate.postForObject(buildUri("friendships/create.json"), 44 | request, WeiboProfile.class); 45 | } 46 | 47 | @Override 48 | public WeiboProfile deleteFriend(long uid) { 49 | MultiValueMap request = new LinkedMultiValueMap( 50 | 1); 51 | request.add("uid", String.valueOf(uid)); 52 | return restTemplate.postForObject(buildUri("friendships/destroy.json"), 53 | request, WeiboProfile.class); 54 | } 55 | 56 | private CursoredList fetchUsersList(String url, long uid, 57 | int pageSize, int pageNumber) { 58 | requireAuthorization(); 59 | JsonNode dataNode = restTemplate.getForObject( 60 | uriBuilder(url).queryParam("uid", String.valueOf(uid)) 61 | .queryParam("count", String.valueOf(pageSize)) 62 | .queryParam("cursor", String.valueOf(pageNumber)) 63 | .build(), JsonNode.class); 64 | return deserializeCursoredList(dataNode, WeiboProfile.class, "users"); 65 | } 66 | 67 | @Override 68 | public List getActiveFollowers(long uid) { 69 | requireAuthorization(); 70 | JsonNode jsonNode = restTemplate.getForObject( 71 | buildUri("friendships/followers/active.json", "uid", uid), 72 | JsonNode.class); 73 | return deserializeDataList(jsonNode, WeiboProfile.class); 74 | } 75 | 76 | @Override 77 | public List getActiveFollowers(long uid, int pageSize) { 78 | requireAuthorization(); 79 | JsonNode jsonNode = restTemplate.getForObject( 80 | uriBuilder("friendships/followers/active.json") 81 | .queryParam("uid", String.valueOf(uid)) 82 | .queryParam("count", String.valueOf(pageSize)).build(), 83 | JsonNode.class); 84 | return deserializeDataList(jsonNode, WeiboProfile.class); 85 | } 86 | 87 | @Override 88 | public CursoredList getBilateralFriends(long uid) { 89 | requireAuthorization(); 90 | JsonNode dataNode = restTemplate.getForObject( 91 | buildUri("friendships/friends/bilateral.json", "uid", uid), 92 | JsonNode.class); 93 | return deserializeCursoredList(dataNode, WeiboProfile.class, "users"); 94 | } 95 | 96 | @Override 97 | public CursoredList getBilateralFriends(long uid, 98 | int pageSize, int pageNumber) { 99 | return fetchUsersList("friendships/friends/bilateral.json", uid, 100 | pageSize, pageNumber); 101 | } 102 | 103 | @Override 104 | public CursoredList getCommonFriends(long user1Uid, 105 | long user2Uid) { 106 | requireAuthorization(); 107 | JsonNode dataNode = restTemplate.getForObject( 108 | uriBuilder("friendships/friends/in_common.json") 109 | .queryParam("uid", String.valueOf(user1Uid)) 110 | .queryParam("suid", String.valueOf(user2Uid)).build(), 111 | JsonNode.class); 112 | return deserializeCursoredList(dataNode, WeiboProfile.class, "users"); 113 | } 114 | 115 | @Override 116 | public CursoredList getCommonFriends(long user1Uid, 117 | long user2Uid, int pageSize, int pageNumber) { 118 | requireAuthorization(); 119 | JsonNode dataNode = restTemplate 120 | .getForObject( 121 | uriBuilder("friendships/friends/in_common.json") 122 | .queryParam("uid", String.valueOf(user1Uid)) 123 | .queryParam("suid", String.valueOf(user2Uid)) 124 | .queryParam("count", String.valueOf(pageSize)) 125 | .queryParam("page", String.valueOf(pageNumber)) 126 | .build(), JsonNode.class); 127 | return deserializeCursoredList(dataNode, WeiboProfile.class, "users"); 128 | } 129 | 130 | @Override 131 | public CursoredList getFollowers(long uid) { 132 | requireAuthorization(); 133 | JsonNode dataNode = restTemplate.getForObject( 134 | buildUri("friendships/followers.json", "uid", uid), 135 | JsonNode.class); 136 | return deserializeCursoredList(dataNode, WeiboProfile.class, "users"); 137 | } 138 | 139 | @Override 140 | public CursoredList getFollowers(long uid, int pageSize, 141 | int pageNumber) { 142 | return fetchUsersList("friendships/followers.json", uid, pageSize, 143 | pageNumber); 144 | } 145 | 146 | @Override 147 | public CursoredList getFriends(long uid) { 148 | requireAuthorization(); 149 | JsonNode dataNode = restTemplate.getForObject( 150 | buildUri("friendships/friends.json", "uid", uid), 151 | JsonNode.class); 152 | return deserializeCursoredList(dataNode, WeiboProfile.class, "users"); 153 | } 154 | 155 | @Override 156 | public CursoredList getFriends(long uid, int pageSize, 157 | int pageNumber) { 158 | return fetchUsersList("friendships/friends.json", uid, pageSize, 159 | pageNumber); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/test/resources/json/repostTimeline.json: -------------------------------------------------------------------------------- 1 | { 2 | "reposts" : [{ 3 | "created_at" : "Tue May 31 17:46:55 +0800 2011", 4 | "id" : 11488058246, 5 | "text" : "你好", 6 | "source" : "Sina microblogging", 7 | "favorited" : false, 8 | "truncated" : false, 9 | "in_reply_to_status_id" : "", 10 | "in_reply_to_user_id" : "", 11 | "in_reply_to_screen_name" : "", 12 | "mid" : "5612814510546515491", 13 | "reposts_count" : 8, 14 | "comments_count" : 9, 15 | "geo" : null, 16 | "user" : { 17 | "id" : 1404376560, 18 | "screen_name" : "zaku", 19 | "name" : "zaku", 20 | "province" : "11", 21 | "city" : "5", 22 | "location" : "Beijing's Chaoyang District.", 23 | "description" : "Fifty years of life, is dreamlike; give birth, then you have died, warrior What else regret.", 24 | "url" : "http://blog.sina.com.cn/zaku", 25 | "profile_image_url" : "http://tp1.sinaimg.cn/1404376560/50/0/1", 26 | "domain" : "zaku", 27 | "gender" : "m", 28 | "followers_count" : 1204, 29 | "friends_count" : 447, 30 | "statuses_count" : 2908, 31 | "favourites_count" : 0, 32 | "created_at" : "Fri Aug 28 00:00:00 +0800 2009", 33 | "following" : false, 34 | "allow_all_act_msg" : false, 35 | "remark" : "", 36 | "geo_enabled" : true, 37 | "verified" : false, 38 | "allow_all_comment" : true, 39 | "avatar_large" : "http://tp1.sinaimg.cn/1404376560/180/0/1", 40 | "verified_reason" : "", 41 | "follow_me" : false, 42 | "online_status" : 0, 43 | "bi_followers_count" : 215 44 | }, 45 | "retweeted_status" : { 46 | "created_at" : "Tue May 24 18:04:53 +0800 2011", 47 | "id" : 11142488790, 48 | "text" : "你好", 49 | "source" : "新浪微博", 50 | "favorited" : false, 51 | "truncated" : false, 52 | "in_reply_to_status_id" : "", 53 | "in_reply_to_user_id" : "", 54 | "in_reply_to_screen_name" : "", 55 | "geo" : null, 56 | "mid" : "5610221544300749636", 57 | "annotations" : [], 58 | "reposts_count" : 5, 59 | "comments_count" : 8, 60 | "user" : { 61 | "id" : 1073880650, 62 | "screen_name" : "檀木幻想", 63 | "name" : "檀木幻想", 64 | "province" : "11", 65 | "city" : "5", 66 | "location" : "北京 朝阳区", 67 | "description" : "请访问微博分析家。", 68 | "url" : "http://www.weibo007.com/", 69 | "profile_image_url" : "http://tp3.sinaimg.cn/1073880650/50/1285051202/1", 70 | "domain" : "woodfantasy", 71 | "gender" : "m", 72 | "followers_count" : 723, 73 | "friends_count" : 415, 74 | "statuses_count" : 587, 75 | "favourites_count" : 107, 76 | "created_at" : "Sat Nov 14 00:00:00 +0800 2009", 77 | "following" : true, 78 | "allow_all_act_msg" : true, 79 | "remark" : "", 80 | "geo_enabled" : true, 81 | "verified" : false, 82 | "allow_all_comment" : true, 83 | "avatar_large" : "http://tp3.sinaimg.cn/1073880650/180/1285051202/1", 84 | "verified_reason" : "", 85 | "follow_me" : true, 86 | "online_status" : 0, 87 | "bi_followers_count" : 199 88 | } 89 | } 90 | }, { 91 | "created_at" : "Tue May 31 17:56:55 +0800 2011", 92 | "id" : 11488058246, 93 | "text" : "seeking attention.", 94 | "source" : "Sina microblogging", 95 | "favorited" : false, 96 | "truncated" : false, 97 | "in_reply_to_status_id" : "", 98 | "in_reply_to_user_id" : "", 99 | "in_reply_to_screen_name" : "", 100 | "geo" : null, 101 | "mid" : "5612814510546515491", 102 | "reposts_count" : 8, 103 | "comments_count" : 9, 104 | "annotations" : [{ 105 | "server_ip" : "10.75.15.190" 106 | } 107 | ], 108 | "geo" : { 109 | "type" : "Point", 110 | "coordinates" : [30.722584, 104.033936] 111 | }, 112 | "user" : { 113 | "id" : 1404376560, 114 | "screen_name" : "zaku", 115 | "name" : "zaku", 116 | "province" : "11", 117 | "city" : "5", 118 | "location" : "Beijing's Chaoyang District.", 119 | "description" : "Fifty years of life, is dreamlike; give birth, then you have died, warrior What else regret.", 120 | "url" : "http://blog.sina.com.cn/zaku", 121 | "profile_image_url" : "http://tp1.sinaimg.cn/1404376560/50/0/1", 122 | "domain" : "zaku", 123 | "gender" : "m", 124 | "followers_count" : 1204, 125 | "friends_count" : 447, 126 | "statuses_count" : 2908, 127 | "favourites_count" : 0, 128 | "created_at" : "Fri Aug 28 00:00:00 +0800 2009", 129 | "following" : false, 130 | "allow_all_act_msg" : false, 131 | "remark" : "", 132 | "geo_enabled" : true, 133 | "verified" : false, 134 | "allow_all_comment" : true, 135 | "avatar_large" : "http://tp1.sinaimg.cn/1404376560/180/0/1", 136 | "verified_reason" : "", 137 | "follow_me" : false, 138 | "online_status" : 0, 139 | "bi_followers_count" : 215 140 | }, 141 | "retweeted_status" : { 142 | "created_at" : "Tue May 24 18:04:53 +0800 2011", 143 | "id" : 11142488790, 144 | "text" : "你好", 145 | "source" : "新浪微博", 146 | "favorited" : false, 147 | "truncated" : false, 148 | "in_reply_to_status_id" : "", 149 | "in_reply_to_user_id" : "", 150 | "in_reply_to_screen_name" : "", 151 | "geo" : null, 152 | "mid" : "5610221544300749636", 153 | "annotations" : [], 154 | "reposts_count" : 5, 155 | "comments_count" : 8, 156 | "user" : { 157 | "id" : 1073880650, 158 | "screen_name" : "檀木幻想", 159 | "name" : "檀木幻想", 160 | "province" : "11", 161 | "city" : "5", 162 | "location" : "北京 朝阳区", 163 | "description" : "请访问微博分析家。", 164 | "url" : "http://www.weibo007.com/", 165 | "profile_image_url" : "http://tp3.sinaimg.cn/1073880650/50/1285051202/1", 166 | "domain" : "woodfantasy", 167 | "gender" : "m", 168 | "followers_count" : 723, 169 | "friends_count" : 415, 170 | "statuses_count" : 587, 171 | "favourites_count" : 107, 172 | "created_at" : "Sat Nov 14 00:00:00 +0800 2009", 173 | "following" : true, 174 | "allow_all_act_msg" : true, 175 | "remark" : "", 176 | "geo_enabled" : true, 177 | "verified" : false, 178 | "allow_all_comment" : true, 179 | "avatar_large" : "http://tp3.sinaimg.cn/1073880650/180/1285051202/1", 180 | "verified_reason" : "", 181 | "follow_me" : true, 182 | "online_status" : 0, 183 | "bi_followers_count" : 199 184 | } 185 | } 186 | } 187 | ], 188 | "previous_cursor" : 0, 189 | "next_cursor" : 1, 190 | "total_number" : 81655 191 | } 192 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/social/weibo/api/impl/WeiboTemplate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api.impl; 17 | 18 | import java.nio.charset.Charset; 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | import org.codehaus.jackson.map.ObjectMapper; 23 | import org.springframework.http.MediaType; 24 | import org.springframework.http.converter.ByteArrayHttpMessageConverter; 25 | import org.springframework.http.converter.FormHttpMessageConverter; 26 | import org.springframework.http.converter.HttpMessageConverter; 27 | import org.springframework.http.converter.ResourceHttpMessageConverter; 28 | import org.springframework.http.converter.StringHttpMessageConverter; 29 | import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter; 30 | import org.springframework.social.oauth2.AbstractOAuth2ApiBinding; 31 | import org.springframework.social.oauth2.OAuth2Version; 32 | import org.springframework.social.support.ClientHttpRequestFactorySelector; 33 | import org.springframework.social.weibo.api.AccountOperations; 34 | import org.springframework.social.weibo.api.CommentOperations; 35 | import org.springframework.social.weibo.api.FavoriteOperations; 36 | import org.springframework.social.weibo.api.FriendOperations; 37 | import org.springframework.social.weibo.api.TimelineOperations; 38 | import org.springframework.social.weibo.api.TrendOperations; 39 | import org.springframework.social.weibo.api.UserOperations; 40 | import org.springframework.social.weibo.api.Weibo; 41 | import org.springframework.social.weibo.api.impl.json.WeiboModule; 42 | import org.springframework.web.client.RestTemplate; 43 | 44 | public class WeiboTemplate extends AbstractOAuth2ApiBinding implements Weibo { 45 | 46 | private AccountOperations accountOperations; 47 | 48 | private CommentOperations commentOperations; 49 | 50 | private UserOperations userOperations; 51 | 52 | private TimelineOperations timelineOperations; 53 | 54 | private FriendOperations friendOperations; 55 | 56 | private ObjectMapper objectMapper; 57 | 58 | private FavoriteTemplate favouriteOperations; 59 | 60 | private TrendTemplate trendOperations; 61 | 62 | public WeiboTemplate(String accessToken) { 63 | super(accessToken); 64 | initialize(); 65 | } 66 | 67 | public WeiboTemplate() { 68 | initialize(); 69 | } 70 | 71 | @Override 72 | protected void configureRestTemplate(RestTemplate restTemplate) { 73 | super.configureRestTemplate(restTemplate); 74 | restTemplate.setErrorHandler(new WeiboErrorHandler()); 75 | } 76 | 77 | @Override 78 | protected MappingJacksonHttpMessageConverter getJsonMessageConverter() { 79 | MappingJacksonHttpMessageConverter converter = super 80 | .getJsonMessageConverter(); 81 | objectMapper = new ObjectMapper(); 82 | objectMapper.registerModule(new WeiboModule()); 83 | converter.setObjectMapper(objectMapper); 84 | return converter; 85 | } 86 | 87 | @Override 88 | protected FormHttpMessageConverter getFormMessageConverter() { 89 | FormHttpMessageConverter messageConverter = super 90 | .getFormMessageConverter(); 91 | List> partConverters = new ArrayList>( 92 | 3); 93 | partConverters.add(new ByteArrayHttpMessageConverter()); 94 | StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(); 95 | List mediaTypes = new ArrayList(2); 96 | mediaTypes 97 | .add(new MediaType("text", "plain", Charset.forName("UTF-8"))); 98 | mediaTypes.add(MediaType.ALL); 99 | stringHttpMessageConverter.setSupportedMediaTypes(mediaTypes); 100 | stringHttpMessageConverter.setWriteAcceptCharset(false); 101 | partConverters.add(stringHttpMessageConverter); 102 | partConverters.add(new ResourceHttpMessageConverter()); 103 | messageConverter.setPartConverters(partConverters); 104 | return messageConverter; 105 | } 106 | 107 | private void initialize() { 108 | // Wrap the request factory with a BufferingClientHttpRequestFactory so 109 | // that the error handler can do repeat reads on the response.getBody() 110 | super.setRequestFactory(ClientHttpRequestFactorySelector 111 | .bufferRequests(getRestTemplate().getRequestFactory())); 112 | initSubApis(); 113 | } 114 | 115 | private void initSubApis() { 116 | this.userOperations = new UserTemplate(objectMapper, getRestTemplate(), 117 | isAuthorized()); 118 | this.accountOperations = new AccountTemplate(objectMapper, 119 | getRestTemplate(), isAuthorized()); 120 | this.timelineOperations = new TimelineTemplate(objectMapper, 121 | getRestTemplate(), isAuthorized()); 122 | this.friendOperations = new FriendTemplate(objectMapper, 123 | getRestTemplate(), isAuthorized()); 124 | this.commentOperations = new CommentTemplate(objectMapper, 125 | getRestTemplate(), isAuthorized()); 126 | this.favouriteOperations = new FavoriteTemplate(objectMapper, 127 | getRestTemplate(), isAuthorized()); 128 | this.trendOperations = new TrendTemplate(objectMapper, 129 | getRestTemplate(), isAuthorized()); 130 | } 131 | 132 | @Override 133 | public UserOperations userOperations() { 134 | return userOperations; 135 | } 136 | 137 | @Override 138 | protected OAuth2Version getOAuth2Version() { 139 | return OAuth2Version.BEARER_DRAFT_2; 140 | } 141 | 142 | @Override 143 | public AccountOperations accountOperations() { 144 | return accountOperations; 145 | } 146 | 147 | @Override 148 | public CommentOperations commentOperations() { 149 | return commentOperations; 150 | } 151 | 152 | @Override 153 | public TimelineOperations timelineOperations() { 154 | return timelineOperations; 155 | } 156 | 157 | @Override 158 | public FriendOperations friendOperations() { 159 | return friendOperations; 160 | } 161 | 162 | protected ObjectMapper getObjectMapper() { 163 | return objectMapper; 164 | } 165 | 166 | @Override 167 | public FavoriteOperations favoriteOperations() { 168 | return favouriteOperations; 169 | } 170 | 171 | @Override 172 | public TrendOperations trendOperations() { 173 | return trendOperations; 174 | } 175 | 176 | } 177 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/social/weibo/api/impl/FavoriteTemplateTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api.impl; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | import static org.junit.Assert.assertTrue; 20 | import static org.springframework.http.HttpMethod.GET; 21 | import static org.springframework.http.HttpMethod.POST; 22 | import static org.springframework.social.test.client.RequestMatchers.body; 23 | import static org.springframework.social.test.client.RequestMatchers.header; 24 | import static org.springframework.social.test.client.RequestMatchers.method; 25 | import static org.springframework.social.test.client.RequestMatchers.requestTo; 26 | import static org.springframework.social.test.client.ResponseCreators.withResponse; 27 | 28 | import java.util.Arrays; 29 | import java.util.List; 30 | 31 | import org.junit.Test; 32 | import org.springframework.social.weibo.api.CursoredList; 33 | import org.springframework.social.weibo.api.Favorite; 34 | import org.springframework.social.weibo.api.Favorite.Tag; 35 | import org.springframework.social.weibo.matcher.StatusMatcher; 36 | 37 | public class FavoriteTemplateTest extends AbstractWeiboOperationsTest { 38 | 39 | private FavoriteTemplate favoriteTemplate; 40 | 41 | @Override 42 | public void setUp() { 43 | favoriteTemplate = new FavoriteTemplate(getObjectMapper(), 44 | getRestTemplate(), true); 45 | } 46 | 47 | @Test 48 | public void testCreateFavorite() { 49 | mockServer 50 | .expect(requestTo("https://api.weibo.com/2/favorites/create.json")) 51 | .andExpect(method(POST)) 52 | .andExpect(body("id=1")) 53 | .andExpect(header("Authorization", "OAuth2 accessToken")) 54 | .andRespond( 55 | withResponse(jsonResource("favorite"), responseHeaders)); 56 | verifyFavorite(favoriteTemplate.createFavorite(1)); 57 | } 58 | 59 | @Test 60 | public void testDeleteFavorite() { 61 | mockServer 62 | .expect(requestTo("https://api.weibo.com/2/favorites/destroy.json")) 63 | .andExpect(method(POST)) 64 | .andExpect(body("id=1")) 65 | .andExpect(header("Authorization", "OAuth2 accessToken")) 66 | .andRespond( 67 | withResponse(jsonResource("favorite"), responseHeaders)); 68 | verifyFavorite(favoriteTemplate.deleteFavorite(1)); 69 | } 70 | 71 | @Test 72 | public void testDeleteFavorites() { 73 | mockServer 74 | .expect(requestTo("https://api.weibo.com/2/favorites/destroy_batch.json")) 75 | .andExpect(method(POST)).andExpect(body("ids=1%2C2%2C3")) 76 | .andExpect(header("Authorization", "OAuth2 accessToken")) 77 | .andRespond(withResponse("{\"result\":true}", responseHeaders)); 78 | assertTrue(favoriteTemplate.deleteFavorites(Arrays.asList(1L, 2L, 3L))); 79 | } 80 | 81 | @Test 82 | public void testGetFavorite() { 83 | mockServer 84 | .expect(requestTo("https://api.weibo.com/2/favorites/show.json?id=1")) 85 | .andExpect(method(GET)) 86 | .andExpect(header("Authorization", "OAuth2 accessToken")) 87 | .andRespond( 88 | withResponse(jsonResource("favorite"), responseHeaders)); 89 | verifyFavorite(favoriteTemplate.getFavorite(1)); 90 | } 91 | 92 | @Test 93 | public void testGetFavorites() { 94 | mockServer 95 | .expect(requestTo("https://api.weibo.com/2/favorites.json")) 96 | .andExpect(method(GET)) 97 | .andExpect(header("Authorization", "OAuth2 accessToken")) 98 | .andRespond( 99 | withResponse(jsonResource("cursoredFavorites"), 100 | responseHeaders)); 101 | CursoredList cursoredList = favoriteTemplate.getFavorites(); 102 | assertEquals(16, cursoredList.getTotalNumber()); 103 | assertEquals(2, cursoredList.size()); 104 | Favorite firstFavorite = cursoredList.iterator().next(); 105 | verifyFavorite(firstFavorite); 106 | } 107 | 108 | @Test 109 | public void testGetFavoritesByTag() { 110 | mockServer 111 | .expect(requestTo("https://api.weibo.com/2/favorites/by_tags.json?tid=1")) 112 | .andExpect(method(GET)) 113 | .andExpect(header("Authorization", "OAuth2 accessToken")) 114 | .andRespond( 115 | withResponse(jsonResource("cursoredFavorites"), 116 | responseHeaders)); 117 | CursoredList cursoredList = favoriteTemplate 118 | .getFavoritesByTag(1); 119 | assertEquals(16, cursoredList.getTotalNumber()); 120 | assertEquals(2, cursoredList.size()); 121 | Favorite firstFavorite = cursoredList.iterator().next(); 122 | verifyFavorite(firstFavorite); 123 | } 124 | 125 | @Test 126 | public void testGetFavoritesByTagPagination() { 127 | mockServer 128 | .expect(requestTo("https://api.weibo.com/2/favorites/by_tags.json?tid=1&count=20&page=5")) 129 | .andExpect(method(GET)) 130 | .andExpect(header("Authorization", "OAuth2 accessToken")) 131 | .andRespond( 132 | withResponse(jsonResource("cursoredFavorites"), 133 | responseHeaders)); 134 | CursoredList cursoredList = favoriteTemplate 135 | .getFavoritesByTag(1, 20, 5); 136 | assertEquals(16, cursoredList.getTotalNumber()); 137 | assertEquals(2, cursoredList.size()); 138 | Favorite firstFavorite = cursoredList.iterator().next(); 139 | verifyFavorite(firstFavorite); 140 | } 141 | 142 | @Test 143 | public void testGetFavoritesPagination() { 144 | mockServer 145 | .expect(requestTo("https://api.weibo.com/2/favorites.json?count=20&page=5")) 146 | .andExpect(method(GET)) 147 | .andExpect(header("Authorization", "OAuth2 accessToken")) 148 | .andRespond( 149 | withResponse(jsonResource("cursoredFavorites"), 150 | responseHeaders)); 151 | CursoredList cursoredList = favoriteTemplate.getFavorites(20, 152 | 5); 153 | assertEquals(16, cursoredList.getTotalNumber()); 154 | assertEquals(2, cursoredList.size()); 155 | Favorite firstFavorite = cursoredList.iterator().next(); 156 | verifyFavorite(firstFavorite); 157 | } 158 | 159 | @Test 160 | public void testGetTags() { 161 | mockServer 162 | .expect(requestTo("https://api.weibo.com/2/favorites/tags.json")) 163 | .andExpect(method(GET)) 164 | .andExpect(header("Authorization", "OAuth2 accessToken")) 165 | .andRespond( 166 | withResponse(jsonResource("cursoredFavoriteTags"), 167 | responseHeaders)); 168 | CursoredList cursoredList = favoriteTemplate.getTags(); 169 | assertEquals(6, cursoredList.getTotalNumber()); 170 | assertEquals(2, cursoredList.size()); 171 | verifyTag(cursoredList.iterator().next()); 172 | } 173 | 174 | @Test 175 | public void testGetTagsPagination() { 176 | mockServer 177 | .expect(requestTo("https://api.weibo.com/2/favorites/tags.json?count=20&page=5")) 178 | .andExpect(method(GET)) 179 | .andExpect(header("Authorization", "OAuth2 accessToken")) 180 | .andRespond( 181 | withResponse(jsonResource("cursoredFavoriteTags"), 182 | responseHeaders)); 183 | CursoredList cursoredList = favoriteTemplate.getTags(20, 5); 184 | assertEquals(6, cursoredList.getTotalNumber()); 185 | assertEquals(2, cursoredList.size()); 186 | verifyTag(cursoredList.iterator().next()); 187 | } 188 | 189 | @Test 190 | public void testUpdateTags() { 191 | mockServer 192 | .expect(requestTo("https://api.weibo.com/2/favorites/tags/update.json")) 193 | .andExpect(method(POST)) 194 | .andExpect(body("id=1&tags=%E5%A5%BD%2C%E6%BC%82%E4%BA%AE")) 195 | .andExpect(header("Authorization", "OAuth2 accessToken")) 196 | .andRespond( 197 | withResponse(jsonResource("favorite"), responseHeaders)); 198 | verifyFavorite(favoriteTemplate.updateTags(1, Arrays.asList("好", "漂亮"))); 199 | } 200 | 201 | private void verifyFavorite(Favorite favorite) { 202 | List tags = favorite.getTags(); 203 | assertEquals(2, tags.size()); 204 | Tag firstTag = tags.iterator().next(); 205 | verifyTag(firstTag); 206 | assertEquals(1306998976000L, favorite.getFavoritedTime().getTime()); 207 | StatusMatcher.verifyStatus(favorite.getStatus()); 208 | } 209 | 210 | private void verifyTag(Tag firstTag) { 211 | assertEquals(23, firstTag.getId()); 212 | assertEquals("80后", firstTag.getValue()); 213 | assertEquals(25369, firstTag.getCount()); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/social/weibo/api/impl/FriendTemplateTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 France Telecom R&D Beijing Co., Ltd 北京法国电信研发中心有限公司 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.social.weibo.api.impl; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | import static org.springframework.http.HttpMethod.GET; 20 | import static org.springframework.http.HttpMethod.POST; 21 | import static org.springframework.social.test.client.RequestMatchers.body; 22 | import static org.springframework.social.test.client.RequestMatchers.header; 23 | import static org.springframework.social.test.client.RequestMatchers.method; 24 | import static org.springframework.social.test.client.RequestMatchers.requestTo; 25 | import static org.springframework.social.test.client.ResponseCreators.withResponse; 26 | import static org.springframework.social.weibo.api.impl.WeiboProfileMatcher.verifyWeiboProfile; 27 | 28 | import java.util.List; 29 | 30 | import org.junit.Test; 31 | import org.springframework.social.weibo.api.CursoredList; 32 | import org.springframework.social.weibo.api.WeiboProfile; 33 | 34 | public class FriendTemplateTest extends AbstractWeiboOperationsTest { 35 | 36 | private FriendTemplate friendTemplate; 37 | 38 | @Override 39 | public void setUp() { 40 | friendTemplate = new FriendTemplate(getObjectMapper(), 41 | getRestTemplate(), true); 42 | } 43 | 44 | @Test 45 | public void testCreateFriend() { 46 | mockServer 47 | .expect(requestTo("https://api.weibo.com/2/friendships/create.json")) 48 | .andExpect(method(POST)) 49 | .andExpect(body("uid=1")) 50 | .andExpect(header("Authorization", "OAuth2 accessToken")) 51 | .andRespond( 52 | withResponse(jsonResource("profile"), responseHeaders)); 53 | 54 | WeiboProfile user = friendTemplate.createFriend(1L); 55 | verifyWeiboProfile(user); 56 | } 57 | 58 | @Test 59 | public void testGetActiveFollowers() { 60 | long uid = 123L; 61 | mockServer 62 | .expect(requestTo("https://api.weibo.com/2/friendships/followers/active.json?uid=" 63 | + uid)) 64 | .andExpect(method(GET)) 65 | .andRespond( 66 | withResponse(jsonResource("users"), responseHeaders)); 67 | List users = friendTemplate.getActiveFollowers(uid); 68 | assertEquals(2, users.size()); 69 | } 70 | 71 | @Test 72 | public void testGetActiveFollowersPagination() { 73 | long uid = 123L; 74 | mockServer 75 | .expect(requestTo("https://api.weibo.com/2/friendships/followers/active.json?uid=123&count=20")) 76 | .andExpect(method(GET)) 77 | .andRespond( 78 | withResponse(jsonResource("users"), responseHeaders)); 79 | List users = friendTemplate.getActiveFollowers(uid, 20); 80 | assertEquals(2, users.size()); 81 | } 82 | 83 | @Test 84 | public void testGetBilateralFriends() { 85 | long uid = 123L; 86 | mockServer 87 | .expect(requestTo("https://api.weibo.com/2/friendships/friends/bilateral.json?uid=" 88 | + uid)) 89 | .andExpect(method(GET)) 90 | .andRespond( 91 | withResponse(jsonResource("cursoredUsers"), 92 | responseHeaders)); 93 | CursoredList users = friendTemplate 94 | .getBilateralFriends(uid); 95 | assertEquals(2, users.size()); 96 | assertEquals(650, users.getTotalNumber()); 97 | assertEquals(0, users.getPreviousCursor()); 98 | assertEquals(1, users.getNextCursor()); 99 | } 100 | 101 | @Test 102 | public void testGetBilateralFriendsPagination() { 103 | long uid = 123L; 104 | mockServer 105 | .expect(requestTo("https://api.weibo.com/2/friendships/friends/bilateral.json?uid=123&count=20&cursor=5")) 106 | .andExpect(method(GET)) 107 | .andRespond( 108 | withResponse(jsonResource("cursoredUsers"), 109 | responseHeaders)); 110 | CursoredList users = friendTemplate.getBilateralFriends( 111 | uid, 20, 5); 112 | assertEquals(2, users.size()); 113 | assertEquals(650, users.getTotalNumber()); 114 | assertEquals(0, users.getPreviousCursor()); 115 | assertEquals(1, users.getNextCursor()); 116 | } 117 | 118 | @Test 119 | public void testGetCommonFriends() { 120 | mockServer 121 | .expect(requestTo("https://api.weibo.com/2/friendships/friends/in_common.json?uid=123&suid=456")) 122 | .andExpect(method(GET)) 123 | .andRespond( 124 | withResponse(jsonResource("cursoredUsers"), 125 | responseHeaders)); 126 | CursoredList users = friendTemplate.getCommonFriends( 127 | 123L, 456L); 128 | assertEquals(2, users.size()); 129 | assertEquals(650, users.getTotalNumber()); 130 | assertEquals(0, users.getPreviousCursor()); 131 | assertEquals(1, users.getNextCursor()); 132 | } 133 | 134 | @Test 135 | public void testGetCommonFriendsPagination() { 136 | mockServer 137 | .expect(requestTo("https://api.weibo.com/2/friendships/friends/in_common.json?uid=123&suid=456&count=20&page=5")) 138 | .andExpect(method(GET)) 139 | .andRespond( 140 | withResponse(jsonResource("cursoredUsers"), 141 | responseHeaders)); 142 | CursoredList users = friendTemplate.getCommonFriends( 143 | 123L, 456L, 20, 5); 144 | assertEquals(2, users.size()); 145 | assertEquals(650, users.getTotalNumber()); 146 | assertEquals(0, users.getPreviousCursor()); 147 | assertEquals(1, users.getNextCursor()); 148 | } 149 | 150 | @Test 151 | public void testGetFollowers() { 152 | long uid = 123L; 153 | mockServer 154 | .expect(requestTo("https://api.weibo.com/2/friendships/followers.json?uid=" 155 | + uid)) 156 | .andExpect(method(GET)) 157 | .andRespond( 158 | withResponse(jsonResource("cursoredUsers"), 159 | responseHeaders)); 160 | CursoredList users = friendTemplate.getFollowers(uid); 161 | assertEquals(2, users.size()); 162 | assertEquals(650, users.getTotalNumber()); 163 | assertEquals(0, users.getPreviousCursor()); 164 | assertEquals(1, users.getNextCursor()); 165 | } 166 | 167 | @Test 168 | public void testGetFollowersPagination() { 169 | long uid = 123L; 170 | mockServer 171 | .expect(requestTo("https://api.weibo.com/2/friendships/followers.json?uid=123&count=20&cursor=5")) 172 | .andExpect(method(GET)) 173 | .andRespond( 174 | withResponse(jsonResource("cursoredUsers"), 175 | responseHeaders)); 176 | CursoredList users = friendTemplate.getFollowers(uid, 20, 177 | 5); 178 | assertEquals(2, users.size()); 179 | assertEquals(650, users.getTotalNumber()); 180 | assertEquals(0, users.getPreviousCursor()); 181 | assertEquals(1, users.getNextCursor()); 182 | } 183 | 184 | @Test 185 | public void testGetFriends() { 186 | long uid = 123L; 187 | mockServer 188 | .expect(requestTo("https://api.weibo.com/2/friendships/friends.json?uid=" 189 | + uid)) 190 | .andExpect(method(GET)) 191 | .andRespond( 192 | withResponse(jsonResource("cursoredUsers"), 193 | responseHeaders)); 194 | CursoredList friends = friendTemplate.getFriends(uid); 195 | assertEquals(2, friends.size()); 196 | assertEquals(650, friends.getTotalNumber()); 197 | assertEquals(0, friends.getPreviousCursor()); 198 | assertEquals(1, friends.getNextCursor()); 199 | } 200 | 201 | @Test 202 | public void testGetFriendsPagination() { 203 | long uid = 123L; 204 | mockServer 205 | .expect(requestTo("https://api.weibo.com/2/friendships/friends.json?uid=123&count=20&cursor=5")) 206 | .andExpect(method(GET)) 207 | .andRespond( 208 | withResponse(jsonResource("cursoredUsers"), 209 | responseHeaders)); 210 | CursoredList friends = friendTemplate.getFriends(uid, 20, 211 | 5); 212 | assertEquals(2, friends.size()); 213 | assertEquals(650, friends.getTotalNumber()); 214 | assertEquals(0, friends.getPreviousCursor()); 215 | assertEquals(1, friends.getNextCursor()); 216 | } 217 | 218 | @Test 219 | public void testDeleteFriend() { 220 | mockServer 221 | .expect(requestTo("https://api.weibo.com/2/friendships/destroy.json")) 222 | .andExpect(method(POST)) 223 | .andExpect(body("uid=1")) 224 | .andExpect(header("Authorization", "OAuth2 accessToken")) 225 | .andRespond( 226 | withResponse(jsonResource("profile"), responseHeaders)); 227 | 228 | WeiboProfile user = friendTemplate.deleteFriend(1L); 229 | verifyWeiboProfile(user); 230 | } 231 | 232 | } 233 | --------------------------------------------------------------------------------