Removed private API usages
> Hello, > > This is a policy violation notice from Twitter API Policy. Multiple applications registered to your account (or accounts) have been found in violation of the Developer Agreement and Policy: > > Developer Agreement: Section II.A.4 > > Developer Agreement: Section II.E > > Developer Policy: Section I.F.5.a > > To ensure continued API access you must do the following: > > Delete all applications associated with your service that are in violation of Twitter’s multi-key abuse policy > > Eliminate all functionality from your service that allows people to use Twitter client application credentials > > Eliminate all functionality from your service that allows people to access non-public API endpoints > > We look forward to hearing back from you on or before 17 JANUARY 2020. If we don’t receive a response by that time your API access will be permanently suspended. > > Regards, > > Twitter API Policy
This commit is contained in:
parent
1490a874c4
commit
5052d1fc62
|
@ -41,5 +41,5 @@ public interface Twitter extends SearchResources, TimelineResources, TweetResour
|
|||
ListResources, DirectMessagesResources, DirectMessagesEventResources,
|
||||
FriendsFollowersResources, FavoritesResources, SpamReportingResources,
|
||||
SavedSearchesResources, TrendsResources, PlacesGeoResources,
|
||||
HelpResources, MutesResources, TwitterPrivate {
|
||||
HelpResources, MutesResources {
|
||||
}
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.mariotaku.microblog.library.twitter;
|
||||
|
||||
import org.mariotaku.microblog.library.twitter.api.PrivateAccountResources;
|
||||
import org.mariotaku.microblog.library.twitter.api.PrivateActivityResources;
|
||||
import org.mariotaku.microblog.library.twitter.api.PrivateDirectMessagesResources;
|
||||
import org.mariotaku.microblog.library.twitter.api.PrivateFriendsFollowersResources;
|
||||
import org.mariotaku.microblog.library.twitter.api.PrivateSearchResources;
|
||||
import org.mariotaku.microblog.library.twitter.api.PrivateTimelineResources;
|
||||
import org.mariotaku.microblog.library.twitter.api.PrivateTweetResources;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/3/4.
|
||||
*/
|
||||
public interface TwitterPrivate extends PrivateActivityResources, PrivateTweetResources,
|
||||
PrivateTimelineResources, PrivateFriendsFollowersResources, PrivateDirectMessagesResources,
|
||||
PrivateSearchResources, PrivateAccountResources {
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.mariotaku.microblog.library.twitter.api;
|
||||
|
||||
import org.mariotaku.microblog.library.MicroBlogException;
|
||||
import org.mariotaku.microblog.library.twitter.model.PinTweetResult;
|
||||
import org.mariotaku.restfu.annotation.method.POST;
|
||||
import org.mariotaku.restfu.annotation.param.Param;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/8/20.
|
||||
*/
|
||||
public interface PrivateAccountResources extends PrivateResources {
|
||||
|
||||
@POST("/account/pin_tweet.json")
|
||||
PinTweetResult pinTweet(@Param("id") String id) throws MicroBlogException;
|
||||
|
||||
@POST("/account/unpin_tweet.json")
|
||||
PinTweetResult unpinTweet(@Param("id") String id) throws MicroBlogException;
|
||||
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.mariotaku.microblog.library.twitter.api;
|
||||
|
||||
import org.mariotaku.microblog.library.MicroBlogException;
|
||||
import org.mariotaku.microblog.library.twitter.model.Activity;
|
||||
import org.mariotaku.microblog.library.twitter.model.CursorTimestampResponse;
|
||||
import org.mariotaku.microblog.library.twitter.model.Paging;
|
||||
import org.mariotaku.microblog.library.twitter.model.ResponseList;
|
||||
import org.mariotaku.microblog.library.twitter.template.StatusAnnotationTemplate;
|
||||
import org.mariotaku.restfu.annotation.method.GET;
|
||||
import org.mariotaku.restfu.annotation.method.POST;
|
||||
import org.mariotaku.restfu.annotation.param.Param;
|
||||
import org.mariotaku.restfu.annotation.param.Params;
|
||||
import org.mariotaku.restfu.annotation.param.Queries;
|
||||
import org.mariotaku.restfu.annotation.param.Query;
|
||||
import org.mariotaku.restfu.http.BodyType;
|
||||
|
||||
@SuppressWarnings("RedundantThrows")
|
||||
@Params(template = StatusAnnotationTemplate.class)
|
||||
public interface PrivateActivityResources extends PrivateResources {
|
||||
|
||||
@GET("/activity/about_me.json")
|
||||
ResponseList<Activity> getActivitiesAboutMe(@Query Paging paging) throws MicroBlogException;
|
||||
|
||||
@Queries({})
|
||||
@GET("/activity/about_me/unread.json")
|
||||
CursorTimestampResponse getActivitiesAboutMeUnread(@Query("cursor") boolean cursor) throws MicroBlogException;
|
||||
|
||||
@Queries({})
|
||||
@POST("/activity/about_me/unread.json")
|
||||
@BodyType(BodyType.FORM)
|
||||
CursorTimestampResponse setActivitiesAboutMeUnread(@Param("cursor") long cursor) throws MicroBlogException;
|
||||
|
||||
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.mariotaku.microblog.library.twitter.api;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.mariotaku.microblog.library.MicroBlogException;
|
||||
import org.mariotaku.microblog.library.twitter.model.ConversationTimeline;
|
||||
import org.mariotaku.microblog.library.twitter.model.DMResponse;
|
||||
import org.mariotaku.microblog.library.twitter.model.NewDm;
|
||||
import org.mariotaku.microblog.library.twitter.model.Paging;
|
||||
import org.mariotaku.microblog.library.twitter.model.ResponseCode;
|
||||
import org.mariotaku.microblog.library.twitter.model.UserEvents;
|
||||
import org.mariotaku.microblog.library.twitter.model.UserInbox;
|
||||
import org.mariotaku.microblog.library.twitter.template.DMAnnotationTemplate;
|
||||
import org.mariotaku.restfu.annotation.method.GET;
|
||||
import org.mariotaku.restfu.annotation.method.POST;
|
||||
import org.mariotaku.restfu.annotation.param.Param;
|
||||
import org.mariotaku.restfu.annotation.param.Params;
|
||||
import org.mariotaku.restfu.annotation.param.Path;
|
||||
import org.mariotaku.restfu.annotation.param.Query;
|
||||
import org.mariotaku.restfu.http.BodyType;
|
||||
|
||||
@Params(template = DMAnnotationTemplate.class)
|
||||
public interface PrivateDirectMessagesResources extends PrivateResources {
|
||||
|
||||
@POST("/dm/conversation/{conversation_id}/delete.json")
|
||||
@BodyType(BodyType.FORM)
|
||||
ResponseCode deleteDmConversation(@Path("conversation_id") String conversationId)
|
||||
throws MicroBlogException;
|
||||
|
||||
@POST("/dm/conversation/{conversation_id}/mark_read.json")
|
||||
@BodyType(BodyType.FORM)
|
||||
ResponseCode markDmRead(@Path("conversation_id") String conversationId,
|
||||
@Param("last_read_event_id") String lastReadEventId) throws MicroBlogException;
|
||||
|
||||
@POST("/dm/update_last_seen_event_id.json")
|
||||
@BodyType(BodyType.FORM)
|
||||
ResponseCode updateLastSeenEventId(@Param("last_seen_event_id") String lastSeenEventId) throws MicroBlogException;
|
||||
|
||||
@POST("/dm/conversation/{conversation_id}/update_name.json")
|
||||
@BodyType(BodyType.FORM)
|
||||
ResponseCode updateDmConversationName(@Path("conversation_id") String conversationId,
|
||||
@Param("name") String name) throws MicroBlogException;
|
||||
|
||||
/**
|
||||
* Update DM conversation avatar
|
||||
*
|
||||
* @param conversationId DM conversation ID
|
||||
* @param avatarId Avatar media ID, null for removing avatar
|
||||
* @return HTTP response code
|
||||
*/
|
||||
@POST("/dm/conversation/{conversation_id}/update_avatar.json")
|
||||
@BodyType(BodyType.FORM)
|
||||
ResponseCode updateDmConversationAvatar(@Path("conversation_id") String conversationId,
|
||||
@Param(value = "avatar_id", ignoreOnNull = true) @Nullable String avatarId) throws MicroBlogException;
|
||||
|
||||
@POST("/dm/conversation/{conversation_id}/disable_notifications.json")
|
||||
ResponseCode disableDmConversations(@Path("conversation_id") String conversationId)
|
||||
throws MicroBlogException;
|
||||
|
||||
@POST("/dm/conversation/{conversation_id}/enable_notifications.json")
|
||||
ResponseCode enableDmConversations(@Path("conversation_id") String conversationId)
|
||||
throws MicroBlogException;
|
||||
|
||||
@POST("/dm/new.json")
|
||||
DMResponse sendDm(@Param NewDm newDm) throws MicroBlogException;
|
||||
|
||||
@POST("/dm/conversation/{conversation_id}/add_participants.json")
|
||||
DMResponse addParticipants(@Path("conversation_id") String conversationId,
|
||||
@Param(value = "participant_ids", arrayDelimiter = ',') String[] participantIds)
|
||||
throws MicroBlogException;
|
||||
|
||||
@POST("/dm/destroy.json")
|
||||
ResponseCode destroyDm(@Param("dm_id") String id) throws MicroBlogException;
|
||||
|
||||
@GET("/dm/user_inbox.json")
|
||||
UserInbox getUserInbox(@Query Paging paging) throws MicroBlogException;
|
||||
|
||||
@GET("/dm/user_updates.json")
|
||||
UserEvents getUserUpdates(@Query("cursor") String cursor) throws MicroBlogException;
|
||||
|
||||
@GET("/dm/conversation/{conversation_id}.json")
|
||||
ConversationTimeline getDmConversation(@Path("conversation_id") String conversationId,
|
||||
@Query Paging paging) throws MicroBlogException;
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.mariotaku.microblog.library.twitter.api;
|
||||
|
||||
import org.mariotaku.microblog.library.MicroBlogException;
|
||||
import org.mariotaku.microblog.library.twitter.model.User;
|
||||
import org.mariotaku.restfu.annotation.method.POST;
|
||||
import org.mariotaku.restfu.annotation.param.KeyValue;
|
||||
import org.mariotaku.restfu.annotation.param.Param;
|
||||
import org.mariotaku.restfu.annotation.param.Queries;
|
||||
|
||||
@Queries({@KeyValue(key = "include_entities", valueKey = "include_entities")})
|
||||
public interface PrivateFriendsFollowersResources extends PrivateResources {
|
||||
|
||||
@POST("/friendships/accept.json")
|
||||
User acceptFriendship(@Param("user_id") String userId) throws MicroBlogException;
|
||||
|
||||
@POST("/friendships/accept.json")
|
||||
User acceptFriendshipByScreenName(@Param("screen_name") String screenName) throws MicroBlogException;
|
||||
|
||||
@POST("/friendships/deny.json")
|
||||
User denyFriendship(@Param("user_id") String userId) throws MicroBlogException;
|
||||
|
||||
@POST("/friendships/deny.json")
|
||||
User denyFriendshipByScreenName(@Param("screen_name") String screenName) throws MicroBlogException;
|
||||
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.mariotaku.microblog.library.twitter.api;
|
||||
|
||||
import org.mariotaku.microblog.library.MicroBlogException;
|
||||
import org.mariotaku.microblog.library.twitter.model.IDs;
|
||||
import org.mariotaku.microblog.library.twitter.model.MutedKeyword;
|
||||
import org.mariotaku.microblog.library.twitter.model.PageableResponseList;
|
||||
import org.mariotaku.microblog.library.twitter.model.Paging;
|
||||
import org.mariotaku.microblog.library.twitter.template.UserAnnotationTemplate;
|
||||
import org.mariotaku.restfu.annotation.method.GET;
|
||||
import org.mariotaku.restfu.annotation.param.Params;
|
||||
import org.mariotaku.restfu.annotation.param.Query;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/3/26.
|
||||
*/
|
||||
@Params(template = UserAnnotationTemplate.class)
|
||||
public interface PrivateMutesResources {
|
||||
|
||||
@GET("/mutes/keywords/ids.json")
|
||||
IDs getMutesKeywordsIDs(Paging paging) throws MicroBlogException;
|
||||
|
||||
@GET("/mutes/keywords/list.json")
|
||||
PageableResponseList<MutedKeyword> getMutesKeywordsList(@Query Paging paging) throws MicroBlogException;
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.mariotaku.microblog.library.twitter.api;
|
||||
|
||||
public interface PrivateResources {
|
||||
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.mariotaku.microblog.library.twitter.api;
|
||||
|
||||
import org.mariotaku.microblog.library.MicroBlogException;
|
||||
import org.mariotaku.microblog.library.twitter.model.UniversalSearchQuery;
|
||||
import org.mariotaku.microblog.library.twitter.model.UniversalSearchResult;
|
||||
import org.mariotaku.microblog.library.twitter.template.StatusAnnotationTemplate;
|
||||
import org.mariotaku.restfu.annotation.method.GET;
|
||||
import org.mariotaku.restfu.annotation.param.Params;
|
||||
import org.mariotaku.restfu.annotation.param.Query;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 15/10/21.
|
||||
*/
|
||||
public interface PrivateSearchResources extends PrivateResources {
|
||||
|
||||
@GET("/search/universal.json")
|
||||
@Params(template = StatusAnnotationTemplate.class)
|
||||
UniversalSearchResult universalSearch(@Query UniversalSearchQuery query) throws MicroBlogException;
|
||||
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.mariotaku.microblog.library.twitter.api;
|
||||
|
||||
import org.mariotaku.microblog.library.MicroBlogException;
|
||||
import org.mariotaku.microblog.library.twitter.model.Paging;
|
||||
import org.mariotaku.microblog.library.twitter.model.ResponseList;
|
||||
import org.mariotaku.microblog.library.twitter.model.Status;
|
||||
import org.mariotaku.microblog.library.twitter.template.StatusAnnotationTemplate;
|
||||
import org.mariotaku.restfu.annotation.method.GET;
|
||||
import org.mariotaku.restfu.annotation.param.Params;
|
||||
import org.mariotaku.restfu.annotation.param.Query;
|
||||
|
||||
@SuppressWarnings("RedundantThrows")
|
||||
@Params(template = StatusAnnotationTemplate.class)
|
||||
public interface PrivateTimelineResources extends PrivateResources {
|
||||
|
||||
@GET("/statuses/media_timeline.json")
|
||||
ResponseList<Status> getMediaTimeline(@Query("user_id") String userId, @Query Paging paging) throws MicroBlogException;
|
||||
|
||||
@GET("/statuses/media_timeline.json")
|
||||
ResponseList<Status> getMediaTimelineByScreenName(@Query("screen_name") String screenName, @Query Paging paging) throws MicroBlogException;
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.mariotaku.microblog.library.twitter.api;
|
||||
|
||||
import org.mariotaku.microblog.library.MicroBlogException;
|
||||
import org.mariotaku.microblog.library.twitter.model.Paging;
|
||||
import org.mariotaku.microblog.library.twitter.model.ResponseList;
|
||||
import org.mariotaku.microblog.library.twitter.model.Status;
|
||||
import org.mariotaku.microblog.library.twitter.model.StatusActivitySummary;
|
||||
import org.mariotaku.microblog.library.twitter.model.TranslationResult;
|
||||
import org.mariotaku.microblog.library.twitter.template.StatusAnnotationTemplate;
|
||||
import org.mariotaku.restfu.annotation.method.GET;
|
||||
import org.mariotaku.restfu.annotation.param.Params;
|
||||
import org.mariotaku.restfu.annotation.param.Path;
|
||||
import org.mariotaku.restfu.annotation.param.Query;
|
||||
|
||||
@SuppressWarnings("RedundantThrows")
|
||||
@Params(template = StatusAnnotationTemplate.class)
|
||||
public interface PrivateTweetResources extends PrivateResources {
|
||||
|
||||
@GET("/statuses/{id}/activity/summary.json")
|
||||
StatusActivitySummary getStatusActivitySummary(@Path("id") String statusId) throws MicroBlogException;
|
||||
|
||||
@GET("/conversation/show.json")
|
||||
ResponseList<Status> showConversation(@Query("id") String statusId, @Query Paging paging) throws MicroBlogException;
|
||||
|
||||
@GET("/translations/show.json")
|
||||
TranslationResult showTranslation(@Query("id") String statusId, @Query("dest") String dest) throws MicroBlogException;
|
||||
}
|
|
@ -24,8 +24,7 @@ import android.support.annotation.NonNull;
|
|||
* Created by mariotaku on 15/4/20.
|
||||
*/
|
||||
public enum ConsumerKeyType {
|
||||
TWITTER_FOR_ANDROID, TWITTER_FOR_IPHONE, TWITTER_FOR_IPAD, TWITTER_FOR_MAC,
|
||||
TWITTER_FOR_WINDOWS_PHONE, TWITTER_FOR_GOOGLE_TV, TWEETDECK, UNKNOWN;
|
||||
UNKNOWN;
|
||||
|
||||
@NonNull
|
||||
public static ConsumerKeyType parse(String type) {
|
||||
|
|
|
@ -21,10 +21,8 @@ package org.mariotaku.twidere.model.account;
|
|||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.bluelinelabs.logansquare.annotation.JsonField;
|
||||
import com.bluelinelabs.logansquare.annotation.JsonObject;
|
||||
import com.hannesdorfmann.parcelableplease.annotation.ParcelablePlease;
|
||||
import com.hannesdorfmann.parcelableplease.annotation.ParcelableThisPlease;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/2/26.
|
||||
|
@ -47,17 +45,6 @@ public class TwitterAccountExtras implements Parcelable, AccountExtras {
|
|||
}
|
||||
};
|
||||
|
||||
@JsonField(name = "official_credentials")
|
||||
@ParcelableThisPlease
|
||||
boolean officialCredentials;
|
||||
|
||||
public boolean isOfficialCredentials() {
|
||||
return officialCredentials;
|
||||
}
|
||||
|
||||
public void setIsOfficialCredentials(boolean officialCredentials) {
|
||||
this.officialCredentials = officialCredentials;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
|
@ -72,7 +59,6 @@ public class TwitterAccountExtras implements Parcelable, AccountExtras {
|
|||
@Override
|
||||
public String toString() {
|
||||
return "TwitterAccountExtras{" +
|
||||
"officialCredentials=" + officialCredentials +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -129,23 +129,6 @@ public class AccountUtils {
|
|||
return null;
|
||||
}
|
||||
|
||||
public static boolean isOfficial(@NonNull final Context context, @NonNull final UserKey accountKey) {
|
||||
AccountManager am = AccountManager.get(context);
|
||||
Account account = AccountUtils.findByAccountKey(am, accountKey);
|
||||
if (account == null) return false;
|
||||
return AccountExtensionsKt.isOfficial(account, am, context);
|
||||
}
|
||||
|
||||
public static boolean hasOfficialKeyAccount(Context context) {
|
||||
final AccountManager am = AccountManager.get(context);
|
||||
for (Account account : getAccounts(am)) {
|
||||
if (AccountExtensionsKt.isOfficial(account, am, context)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int getAccountTypeIcon(@Nullable String accountType) {
|
||||
if (accountType == null) return R.drawable.ic_account_logo_twitter;
|
||||
switch (accountType) {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package org.mariotaku.twidere.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
@ -10,14 +9,9 @@ import org.mariotaku.microblog.library.twitter.model.DirectMessage;
|
|||
import org.mariotaku.microblog.library.twitter.model.MediaEntity;
|
||||
import org.mariotaku.microblog.library.twitter.model.UrlEntity;
|
||||
import org.mariotaku.microblog.library.twitter.model.User;
|
||||
import org.mariotaku.twidere.R;
|
||||
import org.mariotaku.twidere.extension.model.api.StatusExtensionsKt;
|
||||
import org.mariotaku.twidere.model.ConsumerKeyType;
|
||||
import org.mariotaku.twidere.model.SpanItem;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.zip.CRC32;
|
||||
|
||||
import kotlin.Pair;
|
||||
|
||||
/**
|
||||
|
@ -102,55 +96,4 @@ public class InternalTwitterContentUtils {
|
|||
}
|
||||
|
||||
|
||||
public static boolean isOfficialKey(final Context context, final String consumerKey,
|
||||
final String consumerSecret) {
|
||||
if (context == null || consumerKey == null || consumerSecret == null) return false;
|
||||
final String[] keySecrets = context.getResources().getStringArray(R.array.values_official_consumer_secret_crc32);
|
||||
final CRC32 crc32 = new CRC32();
|
||||
final byte[] consumerSecretBytes = consumerSecret.getBytes(Charset.forName("UTF-8"));
|
||||
crc32.update(consumerSecretBytes, 0, consumerSecretBytes.length);
|
||||
final long value = crc32.getValue();
|
||||
crc32.reset();
|
||||
for (final String keySecret : keySecrets) {
|
||||
if (Long.parseLong(keySecret, 16) == value) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static String getOfficialKeyName(final Context context, final String consumerKey,
|
||||
final String consumerSecret) {
|
||||
if (context == null || consumerKey == null || consumerSecret == null) return null;
|
||||
final String[] keySecrets = context.getResources().getStringArray(R.array.values_official_consumer_secret_crc32);
|
||||
final String[] keyNames = context.getResources().getStringArray(R.array.names_official_consumer_secret);
|
||||
final CRC32 crc32 = new CRC32();
|
||||
final byte[] consumerSecretBytes = consumerSecret.getBytes(Charset.forName("UTF-8"));
|
||||
crc32.update(consumerSecretBytes, 0, consumerSecretBytes.length);
|
||||
final long value = crc32.getValue();
|
||||
crc32.reset();
|
||||
for (int i = 0, j = keySecrets.length; i < j; i++) {
|
||||
if (Long.parseLong(keySecrets[i], 16) == value) return keyNames[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static ConsumerKeyType getOfficialKeyType(final Context context, final String consumerKey,
|
||||
final String consumerSecret) {
|
||||
if (context == null || consumerKey == null || consumerSecret == null) {
|
||||
return ConsumerKeyType.UNKNOWN;
|
||||
}
|
||||
final String[] keySecrets = context.getResources().getStringArray(R.array.values_official_consumer_secret_crc32);
|
||||
final String[] keyNames = context.getResources().getStringArray(R.array.types_official_consumer_secret);
|
||||
final CRC32 crc32 = new CRC32();
|
||||
final byte[] consumerSecretBytes = consumerSecret.getBytes(Charset.forName("UTF-8"));
|
||||
crc32.update(consumerSecretBytes, 0, consumerSecretBytes.length);
|
||||
final long value = crc32.getValue();
|
||||
crc32.reset();
|
||||
for (int i = 0, j = keySecrets.length; i < j; i++) {
|
||||
if (Long.parseLong(keySecrets[i], 16) == value) {
|
||||
return ConsumerKeyType.parse(keyNames[i]);
|
||||
}
|
||||
}
|
||||
return ConsumerKeyType.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -169,21 +169,6 @@ public class MicroBlogAPIFactory implements TwidereConstants {
|
|||
@WorkerThread
|
||||
@Nullable
|
||||
public static ExtraHeaders getExtraHeaders(Context context, ConsumerKeyType type) {
|
||||
switch (type) {
|
||||
case TWITTER_FOR_ANDROID: {
|
||||
return TwitterAndroidExtraHeaders.INSTANCE;
|
||||
}
|
||||
case TWITTER_FOR_IPHONE:
|
||||
case TWITTER_FOR_IPAD: {
|
||||
return new UserAgentExtraHeaders("Twitter/6.75.2 CFNetwork/811.4.18 Darwin/16.5.0");
|
||||
}
|
||||
case TWITTER_FOR_MAC: {
|
||||
return TwitterMacExtraHeaders.INSTANCE;
|
||||
}
|
||||
case TWEETDECK: {
|
||||
return new UserAgentExtraHeaders(UserAgentUtils.getDefaultUserAgentStringSafe(context));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -70,7 +70,6 @@ import org.mariotaku.microblog.library.mastodon.annotation.AuthScope
|
|||
import org.mariotaku.microblog.library.twitter.TwitterOAuth
|
||||
import org.mariotaku.microblog.library.twitter.auth.BasicAuthorization
|
||||
import org.mariotaku.microblog.library.twitter.auth.EmptyAuthorization
|
||||
import org.mariotaku.microblog.library.twitter.model.Paging
|
||||
import org.mariotaku.microblog.library.twitter.model.User
|
||||
import org.mariotaku.restfu.http.Endpoint
|
||||
import org.mariotaku.restfu.oauth.OAuthToken
|
||||
|
@ -411,7 +410,7 @@ class SignInActivity : BaseActivity(), OnClickListener, TextWatcher,
|
|||
result.addAccount(am, preferences[randomizeAccountNameKey])
|
||||
Analyzer.log(SignIn(true, accountType = result.type,
|
||||
credentialsType = apiConfig.credentialsType,
|
||||
officialKey = result.extras?.official == true))
|
||||
officialKey = false))
|
||||
finishSignIn()
|
||||
}
|
||||
}
|
||||
|
@ -1221,17 +1220,7 @@ class SignInActivity : BaseActivity(), OnClickListener, TextWatcher,
|
|||
}
|
||||
|
||||
private fun getTwitterAccountExtras(twitter: MicroBlog): TwitterAccountExtras {
|
||||
val extras = TwitterAccountExtras()
|
||||
try {
|
||||
// Get Twitter official only resource
|
||||
val paging = Paging()
|
||||
paging.count(1)
|
||||
twitter.getActivitiesAboutMe(paging)
|
||||
extras.setIsOfficialCredentials(true)
|
||||
} catch (e: MicroBlogException) {
|
||||
// Ignore
|
||||
}
|
||||
return extras
|
||||
return TwitterAccountExtras()
|
||||
}
|
||||
|
||||
private fun getMastodonAccountExtras(mastodon: Mastodon): MastodonAccountExtras {
|
||||
|
|
|
@ -16,27 +16,6 @@ import org.mariotaku.twidere.util.text.FanfouValidator
|
|||
import org.mariotaku.twidere.util.text.MastodonValidator
|
||||
import org.mariotaku.twidere.util.text.TwitterValidator
|
||||
|
||||
fun AccountDetails.isOfficial(context: Context): Boolean {
|
||||
val extra = this.extras
|
||||
if (extra is TwitterAccountExtras) {
|
||||
return extra.isOfficialCredentials
|
||||
}
|
||||
val credentials = this.credentials
|
||||
if (credentials is OAuthCredentials) {
|
||||
return InternalTwitterContentUtils.isOfficialKey(context,
|
||||
credentials.consumer_key, credentials.consumer_secret)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
val AccountExtras.official: Boolean
|
||||
get() {
|
||||
if (this is TwitterAccountExtras) {
|
||||
return isOfficialCredentials
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun <T> AccountDetails.newMicroBlogInstance(context: Context, cls: Class<T>): T {
|
||||
return credentials.newMicroBlogInstance(context, type, cls)
|
||||
}
|
||||
|
|
|
@ -100,19 +100,6 @@ fun Account.setPosition(am: AccountManager, position: Int) {
|
|||
am.setUserData(this, ACCOUNT_USER_DATA_POSITION, position.toString())
|
||||
}
|
||||
|
||||
fun Account.isOfficial(am: AccountManager, context: Context): Boolean {
|
||||
val extras = getAccountExtras(am)
|
||||
if (extras is TwitterAccountExtras) {
|
||||
return extras.isOfficialCredentials
|
||||
}
|
||||
val credentials = getCredentials(am)
|
||||
if (credentials is OAuthCredentials) {
|
||||
return InternalTwitterContentUtils.isOfficialKey(context, credentials.consumer_key,
|
||||
credentials.consumer_secret)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun AccountManager.hasInvalidAccount(): Boolean {
|
||||
val accounts = AccountUtils.getAccounts(this)
|
||||
if (accounts.isEmpty()) return false
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.mariotaku.restfu.oauth.OAuthToken
|
|||
import org.mariotaku.restfu.oauth2.OAuth2Authorization
|
||||
import org.mariotaku.twidere.TwidereConstants.DEFAULT_TWITTER_API_URL_FORMAT
|
||||
import org.mariotaku.twidere.annotation.AccountType
|
||||
import org.mariotaku.twidere.model.ConsumerKeyType
|
||||
import org.mariotaku.twidere.model.account.cred.*
|
||||
import org.mariotaku.twidere.util.HttpClientFactory
|
||||
import org.mariotaku.twidere.util.InternalTwitterContentUtils
|
||||
|
@ -148,9 +149,7 @@ fun <T> newMicroBlogInstance(context: Context, endpoint: Endpoint, auth: Authori
|
|||
val factory = RestAPIFactory<MicroBlogException>()
|
||||
val extraHeaders = run {
|
||||
if (auth !is OAuthAuthorization) return@run null
|
||||
val officialKeyType = InternalTwitterContentUtils.getOfficialKeyType(context,
|
||||
auth.consumerKey, auth.consumerSecret)
|
||||
return@run MicroBlogAPIFactory.getExtraHeaders(context, officialKeyType)
|
||||
return@run MicroBlogAPIFactory.getExtraHeaders(context, ConsumerKeyType.UNKNOWN)
|
||||
} ?: UserAgentExtraHeaders(MicroBlogAPIFactory.getTwidereUserAgent(context))
|
||||
val holder = DependencyHolder.get(context)
|
||||
var extraRequestParams: Map<String, String>? = null
|
||||
|
|
|
@ -304,13 +304,10 @@ abstract class AbsActivitiesFragment protected constructor() :
|
|||
override fun onGapClick(holder: GapViewHolder, position: Int) {
|
||||
val activity = adapter.getActivity(position)
|
||||
DebugLog.v(msg = "Load activity gap $activity")
|
||||
if (!AccountUtils.isOfficial(context, activity.account_key)) {
|
||||
// Skip if item is not a status
|
||||
if (activity.action !in Activity.Action.MENTION_ACTIONS) {
|
||||
adapter.removeGapLoadingId(ObjectId(activity.account_key, activity.id))
|
||||
adapter.notifyItemChanged(position)
|
||||
return
|
||||
}
|
||||
if (activity.action !in Activity.Action.MENTION_ACTIONS) {
|
||||
adapter.removeGapLoadingId(ObjectId(activity.account_key, activity.id))
|
||||
adapter.notifyItemChanged(position)
|
||||
return
|
||||
}
|
||||
val accountKeys = arrayOf(activity.account_key)
|
||||
val pagination = arrayOf(SinceMaxPagination.maxId(activity.min_position,
|
||||
|
|
|
@ -60,7 +60,6 @@ import org.mariotaku.twidere.adapter.ArrayAdapter
|
|||
import org.mariotaku.twidere.annotation.CustomTabType
|
||||
import org.mariotaku.twidere.annotation.TabAccountFlags
|
||||
import org.mariotaku.twidere.extension.applyTheme
|
||||
import org.mariotaku.twidere.extension.model.isOfficial
|
||||
import org.mariotaku.twidere.model.AccountDetails
|
||||
import org.mariotaku.twidere.model.Tab
|
||||
import org.mariotaku.twidere.model.tab.DrawableHolder
|
||||
|
@ -290,7 +289,7 @@ class CustomTabsFragment : BaseFragment(), LoaderCallbacks<Cursor?>, MultiChoice
|
|||
}
|
||||
val officialKeyOnly = arguments.getBoolean(EXTRA_OFFICIAL_KEY_ONLY, false)
|
||||
accountsAdapter.addAll(AccountUtils.getAllAccountDetails(AccountManager.get(context), true).filter {
|
||||
if (officialKeyOnly && !it.isOfficial(context)) {
|
||||
if (officialKeyOnly) {
|
||||
return@filter false
|
||||
}
|
||||
return@filter conf.checkAccountAvailability(it)
|
||||
|
|
|
@ -61,7 +61,6 @@ import org.mariotaku.ktextension.spannable
|
|||
import org.mariotaku.library.objectcursor.ObjectCursor
|
||||
import org.mariotaku.microblog.library.MicroBlog
|
||||
import org.mariotaku.microblog.library.MicroBlogException
|
||||
import org.mariotaku.microblog.library.twitter.TwitterUpload
|
||||
import org.mariotaku.pickncrop.library.MediaPickerActivity
|
||||
import org.mariotaku.sqliteqb.library.Expression
|
||||
import org.mariotaku.twidere.R
|
||||
|
@ -91,8 +90,6 @@ import org.mariotaku.twidere.model.ParcelableMessageConversation.ConversationTyp
|
|||
import org.mariotaku.twidere.model.ParcelableMessageConversation.ExtrasType
|
||||
import org.mariotaku.twidere.model.util.AccountUtils
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations
|
||||
import org.mariotaku.twidere.task.twitter.UpdateStatusTask
|
||||
import org.mariotaku.twidere.task.twitter.message.AddParticipantsTask
|
||||
import org.mariotaku.twidere.task.twitter.message.ClearMessagesTask
|
||||
import org.mariotaku.twidere.task.twitter.message.DestroyConversationTask
|
||||
import org.mariotaku.twidere.task.twitter.message.SetConversationNotificationDisabledTask
|
||||
|
@ -186,12 +183,6 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment,
|
|||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
when (requestCode) {
|
||||
REQUEST_CONVERSATION_ADD_USER -> {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
val user = data.getParcelableExtra<ParcelableUser>(EXTRA_USER)
|
||||
performAddParticipant(user)
|
||||
}
|
||||
}
|
||||
REQUEST_PICK_MEDIA -> {
|
||||
when (resultCode) {
|
||||
Activity.RESULT_OK -> {
|
||||
|
@ -325,19 +316,6 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment,
|
|||
TaskStarter.execute(task)
|
||||
}
|
||||
|
||||
private fun performAddParticipant(user: ParcelableUser) {
|
||||
ProgressDialogFragment.show(childFragmentManager, "add_participant_progress")
|
||||
val weakThis = WeakReference(this)
|
||||
val task = AddParticipantsTask(context, accountKey, conversationId, listOf(user))
|
||||
task.callback = callback@ { succeed ->
|
||||
val f = weakThis.get() ?: return@callback
|
||||
f.dismissDialogThen("add_participant_progress") {
|
||||
loaderManager.restartLoader(0, null, this)
|
||||
}
|
||||
}
|
||||
TaskStarter.execute(task)
|
||||
}
|
||||
|
||||
private fun performSetNotificationDisabled(disabled: Boolean) {
|
||||
ProgressDialogFragment.show(childFragmentManager, "set_notifications_disabled_progress")
|
||||
val weakThis = WeakReference(this)
|
||||
|
@ -379,9 +357,6 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment,
|
|||
val context = fragment.context
|
||||
when (account.type) {
|
||||
AccountType.TWITTER -> {
|
||||
if (account.isOfficial(context)) {
|
||||
return@updateAction microBlog.updateDmConversationName(conversationId, name).isSuccessful
|
||||
}
|
||||
}
|
||||
}
|
||||
throw UnsupportedOperationException()
|
||||
|
@ -391,53 +366,6 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment,
|
|||
}
|
||||
|
||||
private fun performSetConversationAvatar(uri: Uri?) {
|
||||
val conversationId = this.conversationId
|
||||
performUpdateInfo("set_avatar_progress", updateAction = updateAction@ { fragment, account, microBlog ->
|
||||
val context = fragment.context
|
||||
when (account.type) {
|
||||
AccountType.TWITTER -> {
|
||||
if (account.isOfficial(context)) {
|
||||
val upload = account.newMicroBlogInstance(context, cls = TwitterUpload::class.java)
|
||||
if (uri == null) {
|
||||
val result = microBlog.updateDmConversationAvatar(conversationId, null)
|
||||
if (result.isSuccessful) {
|
||||
val dmResponse = microBlog.getDmConversation(conversationId, null).conversationTimeline
|
||||
return@updateAction dmResponse.conversations[conversationId]?.avatarImageHttps
|
||||
}
|
||||
throw MicroBlogException("Error ${result.responseCode}")
|
||||
}
|
||||
var deleteAlways: List<UpdateStatusTask.MediaDeletionItem>? = null
|
||||
try {
|
||||
val media = arrayOf(ParcelableMediaUpdate().apply {
|
||||
this.uri = uri.toString()
|
||||
this.delete_always = true
|
||||
})
|
||||
val uploadResult = UpdateStatusTask.uploadMicroBlogMediaShared(context,
|
||||
upload, account, media, null, null, true, null)
|
||||
deleteAlways = uploadResult.deleteAlways
|
||||
val avatarId = uploadResult.ids.first()
|
||||
val result = microBlog.updateDmConversationAvatar(conversationId, avatarId)
|
||||
if (result.isSuccessful) {
|
||||
uploadResult.deleteOnSuccess.forEach { it.delete(context) }
|
||||
val dmResponse = microBlog.getDmConversation(conversationId, null).conversationTimeline
|
||||
return@updateAction dmResponse.conversations[conversationId]?.avatarImageHttps
|
||||
}
|
||||
throw MicroBlogException("Error ${result.responseCode}")
|
||||
} catch (e: UpdateStatusTask.UploadException) {
|
||||
e.deleteAlways?.forEach {
|
||||
it.delete(context)
|
||||
}
|
||||
throw e
|
||||
} finally {
|
||||
deleteAlways?.forEach { it.delete(context) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
throw UnsupportedOperationException()
|
||||
}, successAction = { uri ->
|
||||
put(Conversations.CONVERSATION_AVATAR, uri)
|
||||
})
|
||||
}
|
||||
|
||||
private inline fun <T> performUpdateInfo(
|
||||
|
|
|
@ -44,7 +44,6 @@ import org.mariotaku.twidere.R
|
|||
import org.mariotaku.twidere.adapter.SelectableUsersAdapter
|
||||
import org.mariotaku.twidere.constant.IntentConstants.*
|
||||
import org.mariotaku.twidere.constant.nameFirstKey
|
||||
import org.mariotaku.twidere.extension.model.isOfficial
|
||||
import org.mariotaku.twidere.extension.queryOne
|
||||
import org.mariotaku.twidere.extension.text.appendCompat
|
||||
import org.mariotaku.twidere.fragment.BaseFragment
|
||||
|
@ -238,11 +237,7 @@ class MessageNewConversationFragment : BaseFragment(), LoaderCallbacks<List<Parc
|
|||
val account = this.account ?: return
|
||||
val selected = this.selectedRecipients
|
||||
if (selected.isEmpty()) return
|
||||
val maxParticipants = if (account.isOfficial(context)) {
|
||||
defaultFeatures.twitterDirectMessageMaxParticipants
|
||||
} else {
|
||||
1
|
||||
}
|
||||
val maxParticipants = 1
|
||||
if (selected.size > maxParticipants) {
|
||||
editParticipants.error = getString(R.string.error_message_message_too_many_participants)
|
||||
return
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.fragment.status
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.FragmentManager
|
||||
import org.mariotaku.abstask.library.TaskStarter
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_STATUS
|
||||
import org.mariotaku.twidere.model.ParcelableStatus
|
||||
import org.mariotaku.twidere.task.status.PinStatusTask
|
||||
|
||||
class PinStatusDialogFragment : AbsSimpleStatusOperationDialogFragment() {
|
||||
|
||||
override val title: String?
|
||||
get() = getString(R.string.title_pin_status_confirm)
|
||||
override val message: String
|
||||
get() = getString(R.string.message_pin_status_confirm)
|
||||
|
||||
override fun onPerformAction(status: ParcelableStatus) {
|
||||
val task = PinStatusTask(context, status.account_key, status.id)
|
||||
TaskStarter.execute(task)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val FRAGMENT_TAG = "pin_status"
|
||||
|
||||
fun show(fm: FragmentManager, status: ParcelableStatus): PinStatusDialogFragment {
|
||||
val args = Bundle()
|
||||
args.putParcelable(EXTRA_STATUS, status)
|
||||
val f = PinStatusDialogFragment()
|
||||
f.arguments = args
|
||||
f.show(fm, FRAGMENT_TAG)
|
||||
return f
|
||||
}
|
||||
}
|
||||
}
|
|
@ -123,7 +123,6 @@ class StatusFragment : BaseFragment(), LoaderCallbacks<SingleResponse<Parcelable
|
|||
private lateinit var navigationHelper: RecyclerViewNavigationHelper
|
||||
private lateinit var scrollListener: RecyclerViewScrollHandler<StatusDetailsAdapter>
|
||||
|
||||
private var loadTranslationTask: LoadTranslationTask? = null
|
||||
// Data fields
|
||||
private var conversationLoaderInitialized: Boolean = false
|
||||
|
||||
|
@ -494,18 +493,9 @@ class StatusFragment : BaseFragment(), LoaderCallbacks<SingleResponse<Parcelable
|
|||
}
|
||||
|
||||
internal fun loadTranslation(status: ParcelableStatus?) {
|
||||
if (status == null) return
|
||||
if (loadTranslationTask?.isFinished == true) return
|
||||
loadTranslationTask = run {
|
||||
val task = LoadTranslationTask(this, status)
|
||||
TaskStarter.execute(task)
|
||||
return@run task
|
||||
}
|
||||
}
|
||||
|
||||
internal fun reloadTranslation() {
|
||||
loadTranslationTask = null
|
||||
loadTranslation(adapter.status)
|
||||
}
|
||||
|
||||
private fun setConversation(data: List<ParcelableStatus>?) {
|
||||
|
@ -668,37 +658,6 @@ class StatusFragment : BaseFragment(), LoaderCallbacks<SingleResponse<Parcelable
|
|||
}
|
||||
}
|
||||
|
||||
internal class LoadTranslationTask(fragment: StatusFragment, val status: ParcelableStatus) :
|
||||
AbsAccountRequestTask<Any?, TranslationResult, Any?>(fragment.context, status.account_key) {
|
||||
|
||||
private val weakFragment = WeakReference(fragment)
|
||||
|
||||
override fun onExecute(account: AccountDetails, params: Any?): TranslationResult {
|
||||
val twitter = account.newMicroBlogInstance(context, MicroBlog::class.java)
|
||||
val prefDest = preferences.getString(KEY_TRANSLATION_DESTINATION, null).orEmpty()
|
||||
val dest: String
|
||||
if (TextUtils.isEmpty(prefDest)) {
|
||||
dest = twitter.accountSettings.language
|
||||
val editor = preferences.edit()
|
||||
editor.putString(KEY_TRANSLATION_DESTINATION, dest)
|
||||
editor.apply()
|
||||
} else {
|
||||
dest = prefDest
|
||||
}
|
||||
return twitter.showTranslation(status.originalId, dest)
|
||||
}
|
||||
|
||||
override fun onSucceed(callback: Any?, result: TranslationResult) {
|
||||
val fragment = weakFragment.get() ?: return
|
||||
fragment.displayTranslation(result)
|
||||
}
|
||||
|
||||
override fun onException(callback: Any?, exception: MicroBlogException) {
|
||||
Toast.makeText(context, exception.getErrorMessage(context), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class StatusActivitySummaryLoader(
|
||||
context: Context,
|
||||
private val accountKey: UserKey,
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.fragment.status
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.FragmentManager
|
||||
import org.mariotaku.abstask.library.TaskStarter
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_STATUS
|
||||
import org.mariotaku.twidere.model.ParcelableStatus
|
||||
import org.mariotaku.twidere.task.status.UnpinStatusTask
|
||||
|
||||
class UnpinStatusDialogFragment : AbsSimpleStatusOperationDialogFragment() {
|
||||
|
||||
override val title: String?
|
||||
get() = getString(R.string.title_unpin_status_confirm)
|
||||
override val message: String
|
||||
get() = getString(R.string.message_unpin_status_confirm)
|
||||
|
||||
override fun onPerformAction(status: ParcelableStatus) {
|
||||
val task = UnpinStatusTask(context, status.account_key, status.id)
|
||||
TaskStarter.execute(task)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val FRAGMENT_TAG = "unpin_status"
|
||||
|
||||
fun show(fm: FragmentManager, status: ParcelableStatus): UnpinStatusDialogFragment {
|
||||
val args = Bundle()
|
||||
args.putParcelable(EXTRA_STATUS, status)
|
||||
val f = UnpinStatusDialogFragment()
|
||||
f.arguments = args
|
||||
f.show(fm, FRAGMENT_TAG)
|
||||
return f
|
||||
}
|
||||
}
|
||||
}
|
|
@ -32,7 +32,6 @@ import org.mariotaku.twidere.loader.users.AbsRequestUsersLoader
|
|||
import org.mariotaku.twidere.loader.users.IncomingFriendshipsLoader
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.model.event.FriendshipTaskEvent
|
||||
import org.mariotaku.twidere.model.util.AccountUtils
|
||||
import org.mariotaku.twidere.view.holder.UserViewHolder
|
||||
|
||||
class IncomingFriendshipsFragment : ParcelableUsersFragment(), IUsersAdapter.RequestClickListener {
|
||||
|
@ -49,8 +48,6 @@ class IncomingFriendshipsFragment : ParcelableUsersFragment(), IUsersAdapter.Req
|
|||
val accountKey = arguments.getParcelable<UserKey?>(EXTRA_ACCOUNT_KEY) ?: return adapter
|
||||
if (USER_TYPE_FANFOU_COM == accountKey.host) {
|
||||
adapter.requestClickListener = this
|
||||
} else if (AccountUtils.isOfficial(context, accountKey)) {
|
||||
adapter.requestClickListener = this
|
||||
}
|
||||
return adapter
|
||||
}
|
||||
|
|
|
@ -38,7 +38,6 @@ import org.mariotaku.twidere.extension.atto.filter
|
|||
import org.mariotaku.twidere.extension.atto.firstElementOrNull
|
||||
import org.mariotaku.twidere.extension.model.api.mastodon.toParcelable
|
||||
import org.mariotaku.twidere.extension.model.api.toParcelable
|
||||
import org.mariotaku.twidere.extension.model.isOfficial
|
||||
import org.mariotaku.twidere.extension.model.makeOriginal
|
||||
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
|
||||
import org.mariotaku.twidere.model.AccountDetails
|
||||
|
@ -93,11 +92,6 @@ class ConversationLoader(
|
|||
// val isOfficial = account.isOfficial(context)
|
||||
val isOfficial = false
|
||||
canLoadAllReplies = isOfficial
|
||||
if (isOfficial) {
|
||||
return microBlog.showConversation(status.id, paging).mapMicroBlogToPaginated {
|
||||
it.toParcelable(account, profileImageSize)
|
||||
}
|
||||
}
|
||||
return showConversationCompat(microBlog, account, status, true)
|
||||
}
|
||||
AccountType.STATUSNET -> {
|
||||
|
|
|
@ -27,12 +27,10 @@ import org.mariotaku.microblog.library.MicroBlogException
|
|||
import org.mariotaku.microblog.library.twitter.model.Paging
|
||||
import org.mariotaku.microblog.library.twitter.model.SearchQuery
|
||||
import org.mariotaku.microblog.library.twitter.model.Status
|
||||
import org.mariotaku.microblog.library.twitter.model.UniversalSearchQuery
|
||||
import org.mariotaku.twidere.annotation.AccountType
|
||||
import org.mariotaku.twidere.annotation.FilterScope
|
||||
import org.mariotaku.twidere.extension.model.api.toParcelable
|
||||
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
|
||||
import org.mariotaku.twidere.extension.model.official
|
||||
import org.mariotaku.twidere.model.AccountDetails
|
||||
import org.mariotaku.twidere.model.ParcelableStatus
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
|
@ -78,9 +76,6 @@ open class MediaStatusesSearchLoader(
|
|||
|
||||
protected open fun processQuery(details: AccountDetails, query: String): String {
|
||||
if (details.type == AccountType.TWITTER) {
|
||||
if (details.extras?.official == true) {
|
||||
return TweetSearchLoader.smQuery("$query filter:media", pagination)
|
||||
}
|
||||
return "$query filter:media exclude:retweets"
|
||||
}
|
||||
return query
|
||||
|
@ -92,15 +87,6 @@ open class MediaStatusesSearchLoader(
|
|||
val microBlog = account.newMicroBlogInstance(context, MicroBlog::class.java)
|
||||
when (account.type) {
|
||||
AccountType.TWITTER -> {
|
||||
if (account.extras?.official == true) {
|
||||
val universalQuery = UniversalSearchQuery(queryText)
|
||||
universalQuery.setModules(UniversalSearchQuery.Module.TWEET)
|
||||
universalQuery.setResultType(UniversalSearchQuery.ResultType.RECENT)
|
||||
universalQuery.setPaging(paging)
|
||||
val searchResult = microBlog.universalSearch(universalQuery)
|
||||
return searchResult.modules.mapNotNull { it.status?.data }
|
||||
}
|
||||
|
||||
val searchQuery = SearchQuery(queryText)
|
||||
searchQuery.paging(paging)
|
||||
return microBlog.search(searchQuery)
|
||||
|
|
|
@ -34,7 +34,6 @@ import org.mariotaku.twidere.extension.model.api.mastodon.mapToPaginated
|
|||
import org.mariotaku.twidere.extension.model.api.mastodon.toParcelable
|
||||
import org.mariotaku.twidere.extension.model.api.toParcelable
|
||||
import org.mariotaku.twidere.extension.model.api.updateFilterInfoForUserTimeline
|
||||
import org.mariotaku.twidere.extension.model.isOfficial
|
||||
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
|
||||
import org.mariotaku.twidere.model.AccountDetails
|
||||
import org.mariotaku.twidere.model.ParcelableStatus
|
||||
|
@ -90,32 +89,23 @@ class MediaTimelineLoader(
|
|||
val microBlog = account.newMicroBlogInstance(context, MicroBlog::class.java)
|
||||
when (account.type) {
|
||||
AccountType.TWITTER -> {
|
||||
if (account.isOfficial(context)) {
|
||||
if (userKey != null) {
|
||||
return microBlog.getMediaTimeline(userKey.id, paging)
|
||||
}
|
||||
if (screenName != null) {
|
||||
return microBlog.getMediaTimelineByScreenName(screenName, paging)
|
||||
}
|
||||
} else {
|
||||
val screenName = this.screenName ?: run {
|
||||
return@run this.user ?: run fetchUser@ {
|
||||
if (userKey == null) throw MicroBlogException("Invalid parameters")
|
||||
val user = microBlog.tryShowUser(userKey.id, null, account.type)
|
||||
this.user = user
|
||||
return@fetchUser user
|
||||
}.screenName
|
||||
}
|
||||
val query = SearchQuery("from:$screenName filter:media exclude:retweets")
|
||||
query.paging(paging)
|
||||
val result = ResponseList<Status>()
|
||||
microBlog.search(query).filterTo(result) { status ->
|
||||
val user = status.user
|
||||
return@filterTo user.id == userKey?.id
|
||||
|| user.screenName.equals(this.screenName, ignoreCase = true)
|
||||
}
|
||||
return result
|
||||
val screenName = this.screenName ?: run {
|
||||
return@run this.user ?: run fetchUser@ {
|
||||
if (userKey == null) throw MicroBlogException("Invalid parameters")
|
||||
val user = microBlog.tryShowUser(userKey.id, null, account.type)
|
||||
this.user = user
|
||||
return@fetchUser user
|
||||
}.screenName
|
||||
}
|
||||
val query = SearchQuery("from:$screenName filter:media exclude:retweets")
|
||||
query.paging(paging)
|
||||
val result = ResponseList<Status>()
|
||||
microBlog.search(query).filterTo(result) { status ->
|
||||
val user = status.user
|
||||
return@filterTo user.id == userKey?.id
|
||||
|| user.screenName.equals(this.screenName, ignoreCase = true)
|
||||
}
|
||||
return result
|
||||
throw MicroBlogException("Wrong user")
|
||||
}
|
||||
AccountType.FANFOU -> {
|
||||
|
|
|
@ -34,7 +34,6 @@ import org.mariotaku.twidere.extension.model.api.mastodon.mapToPaginated
|
|||
import org.mariotaku.twidere.extension.model.api.mastodon.toParcelable
|
||||
import org.mariotaku.twidere.extension.model.api.toParcelable
|
||||
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
|
||||
import org.mariotaku.twidere.extension.model.official
|
||||
import org.mariotaku.twidere.model.AccountDetails
|
||||
import org.mariotaku.twidere.model.ParcelableStatus
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
|
@ -76,9 +75,6 @@ open class TweetSearchLoader(
|
|||
|
||||
protected open fun processQuery(details: AccountDetails, query: String): String {
|
||||
if (details.type == AccountType.TWITTER) {
|
||||
if (details.extras?.official == true) {
|
||||
return smQuery(query, pagination)
|
||||
}
|
||||
return "$query exclude:retweets"
|
||||
}
|
||||
return query
|
||||
|
@ -108,15 +104,6 @@ open class TweetSearchLoader(
|
|||
val queryText = processQuery(account, query)
|
||||
when (account.type) {
|
||||
AccountType.TWITTER -> {
|
||||
if (account.extras?.official == true) {
|
||||
val universalQuery = UniversalSearchQuery(queryText)
|
||||
universalQuery.setModules(UniversalSearchQuery.Module.TWEET)
|
||||
universalQuery.setResultType(UniversalSearchQuery.ResultType.RECENT)
|
||||
universalQuery.setPaging(paging)
|
||||
val searchResult = microBlog.universalSearch(universalQuery)
|
||||
return searchResult.modules.mapNotNull { it.status?.data }
|
||||
}
|
||||
|
||||
val searchQuery = SearchQuery(queryText)
|
||||
searchQuery.paging(paging)
|
||||
return microBlog.search(searchQuery)
|
||||
|
|
|
@ -21,7 +21,6 @@ package org.mariotaku.twidere.loader.statuses
|
|||
|
||||
import android.content.Context
|
||||
import org.mariotaku.twidere.annotation.AccountType
|
||||
import org.mariotaku.twidere.extension.model.official
|
||||
import org.mariotaku.twidere.model.AccountDetails
|
||||
import org.mariotaku.twidere.model.ParcelableStatus
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
|
@ -43,9 +42,6 @@ class UserMentionsLoader(
|
|||
override fun processQuery(details: AccountDetails, query: String): String {
|
||||
val screenName = query.substringAfter("@")
|
||||
if (details.type == AccountType.TWITTER) {
|
||||
if (details.extras?.official == true) {
|
||||
return smQuery("to:$screenName", pagination)
|
||||
}
|
||||
return "to:$screenName exclude:retweets"
|
||||
}
|
||||
return "@$screenName -RT"
|
||||
|
|
|
@ -36,7 +36,6 @@ import org.mariotaku.twidere.extension.api.lookupUsersMapPaginated
|
|||
import org.mariotaku.twidere.extension.model.api.mastodon.mapToPaginated
|
||||
import org.mariotaku.twidere.extension.model.api.mastodon.toParcelable
|
||||
import org.mariotaku.twidere.extension.model.api.toParcelable
|
||||
import org.mariotaku.twidere.extension.model.isOfficial
|
||||
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
|
||||
import org.mariotaku.twidere.model.AccountDetails
|
||||
import org.mariotaku.twidere.model.ParcelableUser
|
||||
|
@ -63,9 +62,7 @@ class StatusFavoritersLoader(
|
|||
}
|
||||
AccountType.TWITTER -> {
|
||||
val microBlog = details.newMicroBlogInstance(context, MicroBlog::class.java)
|
||||
val ids = if (details.isOfficial(context)) {
|
||||
microBlog.getStatusActivitySummary(statusId).favoriters
|
||||
} else {
|
||||
val ids = run {
|
||||
val web = details.newMicroBlogInstance(context, TwitterWeb::class.java)
|
||||
val htmlUsers = web.getFavoritedPopup(statusId).htmlUsers
|
||||
IDsAccessor.setIds(IDs(), parseUserIds(htmlUsers))
|
||||
|
|
|
@ -29,7 +29,6 @@ import org.mariotaku.twidere.annotation.AccountType
|
|||
import org.mariotaku.twidere.annotation.TabAccountFlags
|
||||
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_MENTIONS_ONLY
|
||||
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_MY_FOLLOWING_ONLY
|
||||
import org.mariotaku.twidere.extension.model.isOfficial
|
||||
import org.mariotaku.twidere.fragment.InteractionsTimelineFragment
|
||||
import org.mariotaku.twidere.model.AccountDetails
|
||||
import org.mariotaku.twidere.model.Tab
|
||||
|
@ -105,11 +104,11 @@ class InteractionsTabConfiguration : TabConfiguration() {
|
|||
val am = AccountManager.get(context)
|
||||
val accounts = AccountUtils.getAllAccountDetails(am, false)
|
||||
interactionsAvailable = accounts.any { it.supportsInteractions }
|
||||
requiresStreaming = accounts.all { it.requiresStreaming }
|
||||
requiresStreaming = accounts.all { true }
|
||||
} else when (account.type) {
|
||||
AccountType.TWITTER -> {
|
||||
interactionsAvailable = true
|
||||
requiresStreaming = !account.isOfficial(context)
|
||||
requiresStreaming = true
|
||||
}
|
||||
AccountType.MASTODON -> {
|
||||
interactionsAvailable = true
|
||||
|
@ -161,7 +160,7 @@ class InteractionsTabConfiguration : TabConfiguration() {
|
|||
get() = type == AccountType.TWITTER || type == AccountType.MASTODON
|
||||
|
||||
private val AccountDetails.requiresStreaming: Boolean
|
||||
get() = !isOfficial(context)
|
||||
get() = true
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -194,11 +194,6 @@ class StreamingService : BaseService() {
|
|||
}
|
||||
|
||||
private fun newStreamingRunnable(account: AccountDetails, preferences: AccountPreferences): StreamingRunnable<*>? {
|
||||
when (account.type) {
|
||||
AccountType.TWITTER -> {
|
||||
return TwitterStreamingRunnable(this, account, preferences)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
|
@ -237,236 +232,6 @@ class StreamingService : BaseService() {
|
|||
abstract fun onCancelled()
|
||||
}
|
||||
|
||||
internal inner class TwitterStreamingRunnable(
|
||||
context: Context,
|
||||
account: AccountDetails,
|
||||
accountPreferences: AccountPreferences
|
||||
) : StreamingRunnable<TwitterUserStream>(context, account, accountPreferences) {
|
||||
|
||||
private val profileImageSize = context.getString(R.string.profile_image_size)
|
||||
private val isOfficial = account.isOfficial(context)
|
||||
|
||||
private var canGetInteractions: Boolean = true
|
||||
private var canGetMessages: Boolean = true
|
||||
|
||||
private val interactionsTimeoutRunnable = Runnable {
|
||||
canGetInteractions = true
|
||||
}
|
||||
|
||||
private val messagesTimeoutRunnable = Runnable {
|
||||
canGetMessages = true
|
||||
}
|
||||
|
||||
val callback = object : TwitterTimelineStreamCallback(account.key.id) {
|
||||
|
||||
private var lastStatusTimestamps = LongArray(2)
|
||||
|
||||
private var homeInsertGap = false
|
||||
private var interactionsInsertGap = false
|
||||
|
||||
private var lastActivityAboutMe: ParcelableActivity? = null
|
||||
|
||||
override fun onConnected(): Boolean {
|
||||
homeInsertGap = true
|
||||
interactionsInsertGap = true
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onHomeTimeline(status: Status): Boolean {
|
||||
if (!accountPreferences.isStreamHomeTimelineEnabled) {
|
||||
homeInsertGap = true
|
||||
return false
|
||||
}
|
||||
val parcelableStatus = status.toParcelable(account, profileImageSize = profileImageSize)
|
||||
parcelableStatus.is_gap = homeInsertGap
|
||||
|
||||
val currentTimeMillis = System.currentTimeMillis()
|
||||
if (lastStatusTimestamps[0] >= parcelableStatus.timestamp) {
|
||||
val extraValue = (currentTimeMillis - lastStatusTimestamps[1]).coerceAtMost(499)
|
||||
parcelableStatus.position_key = parcelableStatus.timestamp + extraValue
|
||||
} else {
|
||||
parcelableStatus.position_key = parcelableStatus.timestamp
|
||||
}
|
||||
parcelableStatus.inserted_date = currentTimeMillis
|
||||
|
||||
lastStatusTimestamps[0] = parcelableStatus.position_key
|
||||
lastStatusTimestamps[1] = parcelableStatus.inserted_date
|
||||
|
||||
val values = ObjectCursor.valuesCreatorFrom(ParcelableStatus::class.java)
|
||||
.create(parcelableStatus)
|
||||
context.contentResolver.insert(Statuses.CONTENT_URI, values)
|
||||
homeInsertGap = false
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onActivityAboutMe(activity: Activity): Boolean {
|
||||
if (!accountPreferences.isStreamInteractionsEnabled) {
|
||||
interactionsInsertGap = true
|
||||
return false
|
||||
}
|
||||
if (isOfficial) {
|
||||
// Wait for 30 seconds to avoid rate limit
|
||||
if (canGetInteractions) {
|
||||
handler.post { getInteractions() }
|
||||
canGetInteractions = false
|
||||
handler.postDelayed(interactionsTimeoutRunnable, TimeUnit.SECONDS.toMillis(30))
|
||||
}
|
||||
} else {
|
||||
val insertGap: Boolean
|
||||
if (activity.action in Activity.Action.MENTION_ACTIONS) {
|
||||
insertGap = interactionsInsertGap
|
||||
interactionsInsertGap = false
|
||||
} else {
|
||||
insertGap = false
|
||||
}
|
||||
val curActivity = activity.toParcelable(account, insertGap, profileImageSize)
|
||||
curActivity.account_color = account.color
|
||||
curActivity.position_key = curActivity.timestamp
|
||||
var updateId = -1L
|
||||
if (curActivity.action !in Activity.Action.MENTION_ACTIONS) {
|
||||
/* Merge two activities if:
|
||||
* * Not mention/reply/quote
|
||||
* * Same action
|
||||
* * Same source or target or target object
|
||||
*/
|
||||
val lastActivity = this.lastActivityAboutMe
|
||||
if (lastActivity != null && curActivity.action == lastActivity.action) {
|
||||
if (curActivity.reachedCountLimit) {
|
||||
// Skip if more than 10 sources/targets/target_objects
|
||||
} else if (curActivity.isSameSources(lastActivity)) {
|
||||
curActivity.prependTargets(lastActivity)
|
||||
curActivity.prependTargetObjects(lastActivity)
|
||||
updateId = lastActivity._id
|
||||
} else if (curActivity.isSameTarget(lastActivity)) {
|
||||
curActivity.prependSources(lastActivity)
|
||||
curActivity.prependTargets(lastActivity)
|
||||
updateId = lastActivity._id
|
||||
} else if (curActivity.isSameTargetObject(lastActivity)) {
|
||||
curActivity.prependSources(lastActivity)
|
||||
curActivity.prependTargets(lastActivity)
|
||||
updateId = lastActivity._id
|
||||
}
|
||||
if (updateId > 0) {
|
||||
curActivity.min_position = lastActivity.min_position
|
||||
curActivity.min_sort_position = lastActivity.min_sort_position
|
||||
}
|
||||
}
|
||||
}
|
||||
val values = ObjectCursor.valuesCreatorFrom(ParcelableActivity::class.java)
|
||||
.create(curActivity)
|
||||
val resolver = context.contentResolver
|
||||
if (updateId > 0) {
|
||||
val where = Expression.equals(Activities._ID, updateId).sql
|
||||
resolver.update(Activities.AboutMe.CONTENT_URI, values, where, null)
|
||||
curActivity._id = updateId
|
||||
} else {
|
||||
val uri = resolver.insert(Activities.AboutMe.CONTENT_URI, values)
|
||||
if (uri != null) {
|
||||
curActivity._id = uri.lastPathSegment.toLongOr(-1L)
|
||||
}
|
||||
}
|
||||
lastActivityAboutMe = curActivity
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
override fun onDirectMessage(directMessage: DirectMessage): Boolean {
|
||||
if (!accountPreferences.isStreamDirectMessagesEnabled) {
|
||||
return false
|
||||
}
|
||||
if (canGetMessages) {
|
||||
handler.post { getMessages() }
|
||||
canGetMessages = false
|
||||
val timeout = TimeUnit.SECONDS.toMillis(if (isOfficial) 30 else 90)
|
||||
handler.postDelayed(messagesTimeoutRunnable, timeout)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onAllStatus(status: Status) {
|
||||
if (!accountPreferences.isStreamNotificationUsersEnabled) {
|
||||
return
|
||||
}
|
||||
val user = status.user ?: return
|
||||
val userKey = user.key
|
||||
val where = Expression.and(Expression.equalsArgs(CachedRelationships.ACCOUNT_KEY),
|
||||
Expression.equalsArgs(CachedRelationships.USER_KEY),
|
||||
Expression.equals(CachedRelationships.NOTIFICATIONS_ENABLED, 1)).sql
|
||||
val whereArgs = arrayOf(account.key.toString(), userKey.toString())
|
||||
if (context.contentResolver.queryCount(CachedRelationships.CONTENT_URI,
|
||||
where, whereArgs) <= 0) return
|
||||
|
||||
contentNotificationManager.showUserNotification(account.key, status, userKey)
|
||||
}
|
||||
|
||||
override fun onStatusDeleted(event: DeletionEvent): Boolean {
|
||||
val deleteWhere = Expression.and(Expression.likeRaw(Columns.Column(Statuses.ACCOUNT_KEY), "'%@'||?"),
|
||||
Expression.equalsArgs(Columns.Column(Statuses.ID))).sql
|
||||
val deleteWhereArgs = arrayOf(account.key.host, event.id)
|
||||
context.contentResolver.delete(Statuses.CONTENT_URI, deleteWhere, deleteWhereArgs)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onDisconnectNotice(code: Int, reason: String?): Boolean {
|
||||
disconnect()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onException(ex: Throwable): Boolean {
|
||||
DebugLog.w(LOGTAG, msg = "Exception for ${account.key}", tr = ex)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onUnhandledEvent(obj: TwitterStreamObject, json: String) {
|
||||
DebugLog.d(LOGTAG, msg = "Unhandled event ${obj.determine()} for ${account.key}: $json")
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private fun getInteractions() {
|
||||
val task = GetActivitiesAboutMeTask(context)
|
||||
task.params = object : RefreshTaskParam {
|
||||
override val accountKeys: Array<UserKey> = arrayOf(account.key)
|
||||
|
||||
override val pagination by lazy {
|
||||
val keys = accountKeys.toNulls()
|
||||
val sinceIds = DataStoreUtils.getRefreshNewestActivityMaxPositions(context,
|
||||
Activities.AboutMe.CONTENT_URI, keys)
|
||||
val sinceSortIds = DataStoreUtils.getRefreshNewestActivityMaxSortPositions(context,
|
||||
Activities.AboutMe.CONTENT_URI, keys)
|
||||
return@lazy Array(keys.size) { idx ->
|
||||
SinceMaxPagination.sinceId(sinceIds[idx], sinceSortIds[idx])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
TaskStarter.execute(task)
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private fun getMessages() {
|
||||
val task = GetMessagesTask(context)
|
||||
task.params = object : GetMessagesTask.RefreshMessagesTaskParam(context) {
|
||||
override val accountKeys: Array<UserKey> = arrayOf(account.key)
|
||||
}
|
||||
TaskStarter.execute(task)
|
||||
}
|
||||
}
|
||||
|
||||
override fun createStreamingInstance(): TwitterUserStream {
|
||||
return account.newMicroBlogInstance(context, cls = TwitterUserStream::class.java)
|
||||
}
|
||||
|
||||
override fun TwitterUserStream.beginStreaming() {
|
||||
getUserStream(StreamWith.USER, callback)
|
||||
}
|
||||
|
||||
override fun onCancelled() {
|
||||
callback.disconnect()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val NOTIFICATION_SERVICE_STARTED = 1
|
||||
|
|
|
@ -15,6 +15,7 @@ import org.mariotaku.twidere.model.AccountDetails
|
|||
import org.mariotaku.twidere.model.ParcelableUser
|
||||
import org.mariotaku.twidere.model.event.FriendshipTaskEvent
|
||||
import org.mariotaku.twidere.util.Utils
|
||||
import java.lang.UnsupportedOperationException
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/3/11.
|
||||
|
@ -35,9 +36,7 @@ class AcceptFriendshipTask(context: Context) : AbsFriendshipOperationTask(contex
|
|||
return mastodon.getAccount(args.userKey.id).toParcelable(details)
|
||||
}
|
||||
else -> {
|
||||
val twitter = details.newMicroBlogInstance(context, MicroBlog::class.java)
|
||||
return twitter.acceptFriendship(args.userKey.id).toParcelable(details,
|
||||
profileImageSize = profileImageSize)
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import org.mariotaku.twidere.model.AccountDetails
|
|||
import org.mariotaku.twidere.model.ParcelableUser
|
||||
import org.mariotaku.twidere.model.event.FriendshipTaskEvent
|
||||
import org.mariotaku.twidere.util.Utils
|
||||
import java.lang.UnsupportedOperationException
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/3/11.
|
||||
|
@ -35,9 +36,7 @@ class DenyFriendshipTask(context: Context) : AbsFriendshipOperationTask(context,
|
|||
return mastodon.getAccount(args.userKey.id).toParcelable(details)
|
||||
}
|
||||
else -> {
|
||||
val twitter = details.newMicroBlogInstance(context, MicroBlog::class.java)
|
||||
return twitter.denyFriendship(args.userKey.id).toParcelable(details,
|
||||
profileImageSize = profileImageSize)
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.task.status
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import org.mariotaku.microblog.library.MicroBlog
|
||||
import org.mariotaku.microblog.library.twitter.model.PinTweetResult
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
|
||||
import org.mariotaku.twidere.model.AccountDetails
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.model.event.StatusPinEvent
|
||||
import org.mariotaku.twidere.task.AbsAccountRequestTask
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/4/28.
|
||||
*/
|
||||
|
||||
class PinStatusTask(context: Context, accountKey: UserKey, val id: String) : AbsAccountRequestTask<Any?,
|
||||
PinTweetResult, Any?>(context, accountKey) {
|
||||
|
||||
override fun onExecute(account: AccountDetails, params: Any?): PinTweetResult {
|
||||
val twitter = account.newMicroBlogInstance(context, MicroBlog::class.java)
|
||||
return twitter.pinTweet(id)
|
||||
}
|
||||
|
||||
override fun onSucceed(callback: Any?, result: PinTweetResult) {
|
||||
super.onSucceed(callback, result)
|
||||
Toast.makeText(context, R.string.message_toast_status_pinned, Toast.LENGTH_SHORT).show()
|
||||
if (accountKey != null) {
|
||||
bus.post(StatusPinEvent(accountKey, true))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.task.status
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import org.mariotaku.microblog.library.MicroBlog
|
||||
import org.mariotaku.microblog.library.twitter.model.PinTweetResult
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
|
||||
import org.mariotaku.twidere.model.AccountDetails
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.model.event.StatusPinEvent
|
||||
import org.mariotaku.twidere.task.AbsAccountRequestTask
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/4/28.
|
||||
*/
|
||||
|
||||
class UnpinStatusTask(context: Context, accountKey: UserKey, val id: String) : AbsAccountRequestTask<Any?,
|
||||
PinTweetResult, Any?>(context, accountKey) {
|
||||
|
||||
override fun onExecute(account: AccountDetails, params: Any?): PinTweetResult {
|
||||
val twitter = account.newMicroBlogInstance(context, MicroBlog::class.java)
|
||||
return twitter.unpinTweet(id)
|
||||
}
|
||||
|
||||
override fun onSucceed(callback: Any?, result: PinTweetResult) {
|
||||
super.onSucceed(callback, result)
|
||||
Toast.makeText(context, R.string.message_toast_status_unpinned, Toast.LENGTH_SHORT).show()
|
||||
if (accountKey != null) {
|
||||
bus.post(StatusPinEvent(accountKey, false))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -36,7 +36,6 @@ import org.mariotaku.twidere.extension.api.batchGetRelationships
|
|||
import org.mariotaku.twidere.extension.model.api.mastodon.toParcelable
|
||||
import org.mariotaku.twidere.extension.model.api.microblog.toParcelable
|
||||
import org.mariotaku.twidere.extension.model.extractFanfouHashtags
|
||||
import org.mariotaku.twidere.extension.model.isOfficial
|
||||
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
|
||||
import org.mariotaku.twidere.fragment.InteractionsTimelineFragment
|
||||
import org.mariotaku.twidere.model.AccountDetails
|
||||
|
@ -82,28 +81,6 @@ class GetActivitiesAboutMeTask(context: Context) : GetActivitiesTask(context) {
|
|||
notification.status?.tags?.map { it.name }.orEmpty()
|
||||
})
|
||||
}
|
||||
AccountType.TWITTER -> {
|
||||
val microBlog = account.newMicroBlogInstance(context, MicroBlog::class.java)
|
||||
if (account.isOfficial(context)) {
|
||||
val timeline = microBlog.getActivitiesAboutMe(paging)
|
||||
val activities = timeline.map {
|
||||
it.toParcelable(account, profileImageSize = profileImageSize)
|
||||
}
|
||||
|
||||
return GetTimelineResult(account, activities, activities.flatMap {
|
||||
it.sources?.toList().orEmpty()
|
||||
}, timeline.flatMapTo(HashSet()) { activity ->
|
||||
val mapResult = mutableSetOf<String>()
|
||||
activity.targetStatuses?.flatMapTo(mapResult) { status ->
|
||||
status.entities?.hashtags?.map { it.text }.orEmpty()
|
||||
}
|
||||
activity.targetObjectStatuses?.flatMapTo(mapResult) { status ->
|
||||
status.entities?.hashtags?.map { it.text }.orEmpty()
|
||||
}
|
||||
return@flatMapTo mapResult
|
||||
})
|
||||
}
|
||||
}
|
||||
AccountType.FANFOU -> {
|
||||
val microBlog = account.newMicroBlogInstance(context, MicroBlog::class.java)
|
||||
val activities = microBlog.getMentions(paging).map {
|
||||
|
|
|
@ -244,7 +244,7 @@ abstract class GetStatusesTask(
|
|||
if (result == null) return@forEach
|
||||
val account = result.account
|
||||
val task = CacheTimelineResultTask(context, result,
|
||||
account.type == AccountType.STATUSNET || account.isOfficial(context))
|
||||
account.type == AccountType.STATUSNET)
|
||||
TaskStarter.execute(task)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.task.twitter.message
|
||||
|
||||
import android.accounts.AccountManager
|
||||
import android.content.Context
|
||||
import org.mariotaku.ktextension.mapToArray
|
||||
import org.mariotaku.microblog.library.MicroBlog
|
||||
import org.mariotaku.microblog.library.MicroBlogException
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.annotation.AccountType
|
||||
import org.mariotaku.twidere.extension.model.addParticipants
|
||||
import org.mariotaku.twidere.extension.model.isOfficial
|
||||
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
|
||||
import org.mariotaku.twidere.model.AccountDetails
|
||||
import org.mariotaku.twidere.model.ParcelableMessageConversation
|
||||
import org.mariotaku.twidere.model.ParcelableUser
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.model.util.AccountUtils
|
||||
import org.mariotaku.twidere.task.ExceptionHandlingAbstractTask
|
||||
import org.mariotaku.twidere.util.DataStoreUtils
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/2/25.
|
||||
*/
|
||||
|
||||
class AddParticipantsTask(
|
||||
context: Context,
|
||||
val accountKey: UserKey,
|
||||
val conversationId: String,
|
||||
val participants: Collection<ParcelableUser>
|
||||
) : ExceptionHandlingAbstractTask<Unit?, Boolean, MicroBlogException, ((Boolean) -> Unit)?>(context) {
|
||||
|
||||
private val profileImageSize: String = context.getString(R.string.profile_image_size)
|
||||
|
||||
override val exceptionClass = MicroBlogException::class.java
|
||||
|
||||
override fun onExecute(params: Unit?): Boolean {
|
||||
val account = AccountUtils.getAccountDetails(AccountManager.get(context), accountKey, true) ?:
|
||||
throw MicroBlogException("No account")
|
||||
val conversation = DataStoreUtils.findMessageConversation(context, accountKey, conversationId)
|
||||
if (conversation != null && conversation.is_temp) {
|
||||
val addData = GetMessagesTask.DatabaseUpdateData(listOf(conversation), emptyList())
|
||||
conversation.addParticipants(participants)
|
||||
GetMessagesTask.storeMessages(context, addData, account, showNotification = false)
|
||||
// Don't finish too fast
|
||||
Thread.sleep(300L)
|
||||
return true
|
||||
}
|
||||
val microBlog = account.newMicroBlogInstance(context, cls = MicroBlog::class.java)
|
||||
val addData = requestAddParticipants(microBlog, account, conversation)
|
||||
GetMessagesTask.storeMessages(context, addData, account, showNotification = false)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun afterExecute(callback: ((Boolean) -> Unit)?, result: Boolean?, exception: MicroBlogException?) {
|
||||
callback?.invoke(result ?: false)
|
||||
}
|
||||
|
||||
private fun requestAddParticipants(microBlog: MicroBlog, account: AccountDetails, conversation: ParcelableMessageConversation?):
|
||||
GetMessagesTask.DatabaseUpdateData {
|
||||
when (account.type) {
|
||||
AccountType.TWITTER -> {
|
||||
if (account.isOfficial(context)) {
|
||||
val ids = participants.mapToArray { it.key.id }
|
||||
val response = microBlog.addParticipants(conversationId, ids)
|
||||
if (conversation != null) {
|
||||
conversation.addParticipants(participants)
|
||||
return GetMessagesTask.DatabaseUpdateData(listOf(conversation), emptyList())
|
||||
}
|
||||
return GetMessagesTask.createDatabaseUpdateData(context, account, response,
|
||||
profileImageSize)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
throw MicroBlogException("Adding participants is not supported")
|
||||
}
|
||||
|
||||
}
|
|
@ -25,7 +25,6 @@ import org.mariotaku.microblog.library.MicroBlog
|
|||
import org.mariotaku.microblog.library.MicroBlogException
|
||||
import org.mariotaku.sqliteqb.library.Expression
|
||||
import org.mariotaku.twidere.annotation.AccountType
|
||||
import org.mariotaku.twidere.extension.model.isOfficial
|
||||
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
|
||||
import org.mariotaku.twidere.model.AccountDetails
|
||||
import org.mariotaku.twidere.model.ParcelableMessageConversation
|
||||
|
@ -89,9 +88,6 @@ class DestroyConversationTask(
|
|||
private fun requestDestroyConversation(microBlog: MicroBlog, account: AccountDetails): Boolean {
|
||||
when (account.type) {
|
||||
AccountType.TWITTER -> {
|
||||
if (account.isOfficial(context)) {
|
||||
return microBlog.deleteDmConversation(conversationId).isSuccessful
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
|
|
@ -25,7 +25,6 @@ import org.mariotaku.microblog.library.MicroBlog
|
|||
import org.mariotaku.microblog.library.MicroBlogException
|
||||
import org.mariotaku.sqliteqb.library.Expression
|
||||
import org.mariotaku.twidere.annotation.AccountType
|
||||
import org.mariotaku.twidere.extension.model.isOfficial
|
||||
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
|
||||
import org.mariotaku.twidere.model.AccountDetails
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
|
@ -75,9 +74,6 @@ class DestroyMessageTask(
|
|||
account: AccountDetails, messageId: String): Boolean {
|
||||
when (account.type) {
|
||||
AccountType.TWITTER -> {
|
||||
if (account.isOfficial(context)) {
|
||||
return microBlog.destroyDm(messageId).isSuccessful
|
||||
}
|
||||
}
|
||||
}
|
||||
microBlog.destroyDirectMessage(messageId)
|
||||
|
|
|
@ -99,27 +99,11 @@ class GetMessagesTask(
|
|||
// Use fanfou DM api, disabled since it's conversation api is not suitable for paging
|
||||
// return getFanfouMessages(microBlog, details, param, index)
|
||||
}
|
||||
AccountType.TWITTER -> {
|
||||
// Use official DM api
|
||||
if (details.isOfficial(context)) {
|
||||
return getTwitterOfficialMessages(microBlog, details, param, index)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Use default method
|
||||
return getDefaultMessages(microBlog, details, param, index)
|
||||
}
|
||||
|
||||
private fun getTwitterOfficialMessages(microBlog: MicroBlog, details: AccountDetails,
|
||||
param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData {
|
||||
val conversationId = param.conversationId
|
||||
if (conversationId == null) {
|
||||
return getTwitterOfficialUserInbox(microBlog, details, param, index)
|
||||
} else {
|
||||
return getTwitterOfficialConversation(microBlog, details, conversationId, param, index)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFanfouMessages(microBlog: MicroBlog, details: AccountDetails, param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData {
|
||||
val conversationId = param.conversationId
|
||||
if (conversationId == null) {
|
||||
|
@ -195,35 +179,6 @@ class GetMessagesTask(
|
|||
}
|
||||
|
||||
|
||||
private fun getTwitterOfficialConversation(microBlog: MicroBlog, details: AccountDetails,
|
||||
conversationId: String, param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData {
|
||||
val maxId = (param.pagination?.get(index) as? SinceMaxPagination)?.maxId
|
||||
?: return DatabaseUpdateData(emptyList(), emptyList())
|
||||
val paging = Paging().apply {
|
||||
maxId(maxId)
|
||||
}
|
||||
|
||||
val response = microBlog.getDmConversation(conversationId, paging).conversationTimeline
|
||||
return createDatabaseUpdateData(context, details, response, profileImageSize)
|
||||
}
|
||||
|
||||
private fun getTwitterOfficialUserInbox(microBlog: MicroBlog, details: AccountDetails,
|
||||
param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData {
|
||||
val maxId = (param.pagination?.get(index) as? SinceMaxPagination)?.maxId
|
||||
val cursor = (param.pagination?.get(index) as? CursorPagination)?.cursor
|
||||
val response = if (cursor != null) {
|
||||
microBlog.getUserUpdates(cursor).userEvents
|
||||
} else {
|
||||
microBlog.getUserInbox(Paging().apply {
|
||||
if (maxId != null) {
|
||||
maxId(maxId)
|
||||
}
|
||||
}).userInbox
|
||||
} ?: throw MicroBlogException("Null response data")
|
||||
return createDatabaseUpdateData(context, details, response, profileImageSize)
|
||||
}
|
||||
|
||||
|
||||
private fun getFanfouConversations(microBlog: MicroBlog, details: AccountDetails,
|
||||
param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData {
|
||||
val accountKey = details.key
|
||||
|
@ -275,16 +230,10 @@ class GetMessagesTask(
|
|||
defaultKeys, false)
|
||||
val outgoingIds = DataStoreUtils.getNewestMessageIds(context, Messages.CONTENT_URI,
|
||||
defaultKeys, true)
|
||||
val cursors = DataStoreUtils.getNewestConversations(context, Conversations.CONTENT_URI,
|
||||
twitterOfficialKeys).mapToArray { it?.request_cursor }
|
||||
accounts.forEachIndexed { index, details ->
|
||||
if (details == null) return@forEachIndexed
|
||||
if (details.isOfficial(context)) {
|
||||
result[index] = CursorPagination.valueOf(cursors[index])
|
||||
} else {
|
||||
result[index] = SinceMaxPagination.sinceId(incomingIds[index], -1)
|
||||
result[accounts.size + index] = SinceMaxPagination.sinceId(outgoingIds[index], -1)
|
||||
}
|
||||
result[index] = SinceMaxPagination.sinceId(incomingIds[index], -1)
|
||||
result[accounts.size + index] = SinceMaxPagination.sinceId(outgoingIds[index], -1)
|
||||
}
|
||||
return@lazy result
|
||||
}
|
||||
|
@ -301,7 +250,7 @@ class GetMessagesTask(
|
|||
val outgoingIds = DataStoreUtils.getOldestMessageIds(context, Messages.CONTENT_URI,
|
||||
defaultKeys, true)
|
||||
val oldestConversations = DataStoreUtils.getOldestConversations(context,
|
||||
Conversations.CONTENT_URI, twitterOfficialKeys)
|
||||
Conversations.CONTENT_URI, emptyArray())
|
||||
oldestConversations.forEachIndexed { i, conversation ->
|
||||
val extras = conversation?.conversation_extras as? TwitterOfficialConversationExtras ?: return@forEachIndexed
|
||||
incomingIds[i] = extras.maxEntryId
|
||||
|
@ -342,19 +291,6 @@ class GetMessagesTask(
|
|||
protected val defaultKeys: Array<UserKey?> by lazy {
|
||||
return@lazy accounts.map { account ->
|
||||
account ?: return@map null
|
||||
if (account.isOfficial(context)) {
|
||||
return@map null
|
||||
}
|
||||
return@map account.key
|
||||
}.toTypedArray()
|
||||
}
|
||||
|
||||
protected val twitterOfficialKeys: Array<UserKey?> by lazy {
|
||||
return@lazy accounts.map { account ->
|
||||
account ?: return@map null
|
||||
if (!account.isOfficial(context)) {
|
||||
return@map null
|
||||
}
|
||||
return@map account.key
|
||||
}.toTypedArray()
|
||||
}
|
||||
|
|
|
@ -28,7 +28,6 @@ import org.mariotaku.microblog.library.MicroBlogException
|
|||
import org.mariotaku.sqliteqb.library.Expression
|
||||
import org.mariotaku.sqliteqb.library.OrderBy
|
||||
import org.mariotaku.twidere.annotation.AccountType
|
||||
import org.mariotaku.twidere.extension.model.isOfficial
|
||||
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
|
||||
import org.mariotaku.twidere.extension.model.timestamp
|
||||
import org.mariotaku.twidere.extension.queryOne
|
||||
|
@ -81,23 +80,6 @@ class MarkMessageReadTask(
|
|||
internal fun performMarkRead(context: Context, microBlog: MicroBlog, account: AccountDetails,
|
||||
conversation: ParcelableMessageConversation): Pair<String, Long>? {
|
||||
val cr = context.contentResolver
|
||||
when (account.type) {
|
||||
AccountType.TWITTER -> {
|
||||
if (account.isOfficial(context)) {
|
||||
val event = (conversation.conversation_extras as? TwitterOfficialConversationExtras)?.maxReadEvent ?: run {
|
||||
val message = cr.findRecentMessage(account.key, conversation.id) ?: return null
|
||||
return@run Pair(message.id, message.timestamp)
|
||||
}
|
||||
if (conversation.last_read_timestamp > event.second) {
|
||||
// Local is newer, ignore network request
|
||||
return event
|
||||
}
|
||||
if (microBlog.markDmRead(conversation.id, event.first).isSuccessful) {
|
||||
return event
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val message = cr.findRecentMessage(account.key, conversation.id) ?: return null
|
||||
return Pair(message.id, message.timestamp)
|
||||
}
|
||||
|
|
|
@ -31,7 +31,6 @@ import org.mariotaku.sqliteqb.library.Expression
|
|||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.annotation.AccountType
|
||||
import org.mariotaku.twidere.extension.model.api.*
|
||||
import org.mariotaku.twidere.extension.model.isOfficial
|
||||
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
|
||||
import org.mariotaku.twidere.model.AccountDetails
|
||||
import org.mariotaku.twidere.model.ParcelableMedia
|
||||
|
@ -42,7 +41,6 @@ import org.mariotaku.twidere.model.util.ParcelableMessageUtils
|
|||
import org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations
|
||||
import org.mariotaku.twidere.task.ExceptionHandlingAbstractTask
|
||||
import org.mariotaku.twidere.task.twitter.UpdateStatusTask
|
||||
import org.mariotaku.twidere.task.twitter.message.GetMessagesTask
|
||||
import org.mariotaku.twidere.task.twitter.message.GetMessagesTask.Companion.addConversation
|
||||
import org.mariotaku.twidere.task.twitter.message.GetMessagesTask.Companion.addLocalConversations
|
||||
|
||||
|
@ -86,11 +84,7 @@ class SendMessageTask(
|
|||
message: ParcelableNewMessage): GetMessagesTask.DatabaseUpdateData {
|
||||
when (account.type) {
|
||||
AccountType.TWITTER -> {
|
||||
if (account.isOfficial(context)) {
|
||||
return sendTwitterOfficialDM(microBlog, account, message)
|
||||
} else {
|
||||
return sendTwitterMessageEvent(microBlog, account, message)
|
||||
}
|
||||
return sendTwitterMessageEvent(microBlog, account, message)
|
||||
}
|
||||
AccountType.FANFOU -> {
|
||||
return sendFanfouDM(microBlog, account, message)
|
||||
|
@ -99,47 +93,6 @@ class SendMessageTask(
|
|||
return sendDefaultDM(microBlog, account, message)
|
||||
}
|
||||
|
||||
private fun sendTwitterOfficialDM(microBlog: MicroBlog, account: AccountDetails,
|
||||
message: ParcelableNewMessage): GetMessagesTask.DatabaseUpdateData {
|
||||
var deleteOnSuccess: List<UpdateStatusTask.MediaDeletionItem>? = null
|
||||
var deleteAlways: List<UpdateStatusTask.MediaDeletionItem>? = null
|
||||
val sendResponse = try {
|
||||
val conversationId = message.conversation_id
|
||||
val tempConversation = message.is_temp_conversation
|
||||
|
||||
val newDm = NewDm()
|
||||
if (!tempConversation && conversationId != null) {
|
||||
newDm.setConversationId(conversationId)
|
||||
} else {
|
||||
newDm.setRecipientIds(message.recipient_ids)
|
||||
}
|
||||
newDm.setText(message.text)
|
||||
|
||||
if (message.media.isNotNullOrEmpty()) {
|
||||
val upload = account.newMicroBlogInstance(context, cls = TwitterUpload::class.java)
|
||||
val uploadResult = UpdateStatusTask.uploadMicroBlogMediaShared(context,
|
||||
upload, account, message.media, null, null, true, null)
|
||||
newDm.setMediaId(uploadResult.ids[0])
|
||||
deleteAlways = uploadResult.deleteAlways
|
||||
deleteOnSuccess = uploadResult.deleteOnSuccess
|
||||
}
|
||||
microBlog.sendDm(newDm)
|
||||
} catch (e: UpdateStatusTask.UploadException) {
|
||||
e.deleteAlways?.forEach {
|
||||
it.delete(context)
|
||||
}
|
||||
throw MicroBlogException(e)
|
||||
} finally {
|
||||
deleteAlways?.forEach { it.delete(context) }
|
||||
}
|
||||
deleteOnSuccess?.forEach { it.delete(context) }
|
||||
val conversationId = sendResponse.entries?.firstOrNull {
|
||||
it.message != null
|
||||
}?.message?.conversationId
|
||||
val response = microBlog.getDmConversation(conversationId, null).conversationTimeline
|
||||
return GetMessagesTask.createDatabaseUpdateData(context, account, response, profileImageSize)
|
||||
}
|
||||
|
||||
private fun sendTwitterMessageEvent(microBlog: MicroBlog, account: AccountDetails,
|
||||
message: ParcelableNewMessage): GetMessagesTask.DatabaseUpdateData {
|
||||
val recipientId = message.recipient_ids.singleOrNull() ?: throw MicroBlogException("No recipient")
|
||||
|
|
|
@ -24,7 +24,6 @@ import android.content.Context
|
|||
import org.mariotaku.microblog.library.MicroBlog
|
||||
import org.mariotaku.microblog.library.MicroBlogException
|
||||
import org.mariotaku.twidere.annotation.AccountType
|
||||
import org.mariotaku.twidere.extension.model.isOfficial
|
||||
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
|
||||
import org.mariotaku.twidere.extension.model.notificationDisabled
|
||||
import org.mariotaku.twidere.model.AccountDetails
|
||||
|
@ -61,24 +60,6 @@ class SetConversationNotificationDisabledTask(
|
|||
|
||||
private fun requestSetNotificationDisabled(microBlog: MicroBlog, account: AccountDetails):
|
||||
GetMessagesTask.DatabaseUpdateData {
|
||||
when (account.type) {
|
||||
AccountType.TWITTER -> {
|
||||
if (account.isOfficial(context)) {
|
||||
val response = if (notificationDisabled) {
|
||||
microBlog.disableDmConversations(conversationId)
|
||||
} else {
|
||||
microBlog.enableDmConversations(conversationId)
|
||||
}
|
||||
val conversation = DataStoreUtils.findMessageConversation(context, accountKey,
|
||||
conversationId) ?: return GetMessagesTask.DatabaseUpdateData(emptyList(), emptyList())
|
||||
if (response.isSuccessful) {
|
||||
conversation.notificationDisabled = notificationDisabled
|
||||
}
|
||||
return GetMessagesTask.DatabaseUpdateData(listOf(conversation), emptyList())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val conversation = DataStoreUtils.findMessageConversation(context, accountKey,
|
||||
conversationId) ?: return GetMessagesTask.DatabaseUpdateData(emptyList(), emptyList())
|
||||
conversation.notificationDisabled = notificationDisabled
|
||||
|
|
|
@ -377,18 +377,6 @@ class AsyncTwitterWrapper(
|
|||
}
|
||||
|
||||
fun setActivitiesAboutMeUnreadAsync(accountKeys: Array<UserKey>, cursor: Long) {
|
||||
val task = object : ExceptionHandlingAbstractTask<Any?, Unit, MicroBlogException, Any?>(context) {
|
||||
override val exceptionClass = MicroBlogException::class.java
|
||||
|
||||
override fun onExecute(params: Any?) {
|
||||
for (accountKey in accountKeys) {
|
||||
val microBlog = MicroBlogAPIFactory.getInstance(context, accountKey) ?: continue
|
||||
if (!AccountUtils.isOfficial(context, accountKey)) continue
|
||||
microBlog.setActivitiesAboutMeUnread(cursor)
|
||||
}
|
||||
}
|
||||
}
|
||||
TaskStarter.execute(task)
|
||||
}
|
||||
|
||||
fun addUpdatingRelationshipId(accountKey: UserKey, userKey: UserKey) {
|
||||
|
|
|
@ -998,23 +998,8 @@ object DataStoreUtils {
|
|||
|
||||
private fun <T> getOfficialSeparatedIds(context: Context, getFromDatabase: (Array<UserKey?>, Boolean) -> T,
|
||||
mergeResult: (T, T) -> T, accountKeys: Array<UserKey?>): T {
|
||||
val officialKeys = Array(accountKeys.size) {
|
||||
val key = accountKeys[it] ?: return@Array null
|
||||
if (AccountUtils.isOfficial(context, key)) {
|
||||
return@Array key
|
||||
}
|
||||
return@Array null
|
||||
}
|
||||
val notOfficialKeys = Array(accountKeys.size) {
|
||||
val key = accountKeys[it] ?: return@Array null
|
||||
if (AccountUtils.isOfficial(context, key)) {
|
||||
return@Array null
|
||||
}
|
||||
return@Array key
|
||||
}
|
||||
|
||||
val officialMaxPositions = getFromDatabase(officialKeys, true)
|
||||
val notOfficialMaxPositions = getFromDatabase(notOfficialKeys, false)
|
||||
val officialMaxPositions = getFromDatabase(emptyArray(), true)
|
||||
val notOfficialMaxPositions = getFromDatabase(accountKeys, false)
|
||||
return mergeResult(officialMaxPositions, notOfficialMaxPositions)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,7 +54,6 @@ import org.mariotaku.twidere.app.TwidereApplication
|
|||
import org.mariotaku.twidere.constant.favoriteConfirmationKey
|
||||
import org.mariotaku.twidere.constant.iWantMyStarsBackKey
|
||||
import org.mariotaku.twidere.constant.nameFirstKey
|
||||
import org.mariotaku.twidere.extension.model.isOfficial
|
||||
import org.mariotaku.twidere.fragment.AbsStatusesFragment
|
||||
import org.mariotaku.twidere.fragment.AddStatusFilterDialogFragment
|
||||
import org.mariotaku.twidere.fragment.BaseFragment
|
||||
|
@ -189,11 +188,7 @@ object MenuUtils {
|
|||
favorite.setTitle(if (isFavorite) R.string.action_undo_like else R.string.action_like)
|
||||
}
|
||||
}
|
||||
val translate = menu.findItem(R.id.translate)
|
||||
if (translate != null) {
|
||||
val isOfficialKey = details.isOfficial(context)
|
||||
menu.setItemAvailability(R.id.translate, isOfficialKey)
|
||||
}
|
||||
menu.setItemAvailability(R.id.translate, false)
|
||||
menu.removeGroup(MENU_GROUP_STATUS_EXTENSION)
|
||||
addIntentToMenuForExtension(context, menu, MENU_GROUP_STATUS_EXTENSION,
|
||||
INTENT_ACTION_EXTENSION_OPEN_STATUS, EXTRA_STATUS, EXTRA_STATUS_JSON, status)
|
||||
|
@ -284,10 +279,8 @@ object MenuUtils {
|
|||
DestroyStatusDialogFragment.show(fm, status)
|
||||
}
|
||||
R.id.pin -> {
|
||||
PinStatusDialogFragment.show(fm, status)
|
||||
}
|
||||
R.id.unpin -> {
|
||||
UnpinStatusDialogFragment.show(fm, status)
|
||||
}
|
||||
R.id.add_to_filter -> {
|
||||
AddStatusFilterDialogFragment.show(fm, status)
|
||||
|
|
|
@ -339,26 +339,8 @@ class DetailStatusViewHolder(
|
|||
|
||||
|
||||
val lang = status.lang
|
||||
if (CheckUtils.isValidLocale(lang) && account.isOfficial(context)) {
|
||||
translateContainer.visibility = View.VISIBLE
|
||||
if (translation != null) {
|
||||
val locale = Locale(translation.translatedLang)
|
||||
translateLabelView.text = context.getString(R.string.label_translated_to_language,
|
||||
locale.displayLanguage)
|
||||
translateResultView.visibility = View.VISIBLE
|
||||
translateChangeLanguageView.visibility = View.VISIBLE
|
||||
translateResultView.text = translation.text
|
||||
} else {
|
||||
val locale = Locale(lang)
|
||||
translateLabelView.text = context.getString(R.string.label_translate_from_language,
|
||||
locale.displayLanguage)
|
||||
translateResultView.visibility = View.GONE
|
||||
translateChangeLanguageView.visibility = View.GONE
|
||||
}
|
||||
} else {
|
||||
translateLabelView.setText(R.string.unknown_language)
|
||||
translateContainer.visibility = View.GONE
|
||||
}
|
||||
translateLabelView.setText(R.string.unknown_language)
|
||||
translateContainer.visibility = View.GONE
|
||||
|
||||
textView.setTextIsSelectable(true)
|
||||
translateResultView.setTextIsSelectable(true)
|
||||
|
|
|
@ -25,40 +25,6 @@
|
|||
<item>true</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="names_official_consumer_secret">
|
||||
<!--Twitter for Android-->
|
||||
<item>Twitter for Android</item>
|
||||
<!--Twitter for iPhone-->
|
||||
<item>Twitter for iPhone</item>
|
||||
<!--Twitter for iPad-->
|
||||
<item>Twitter for iPad</item>
|
||||
<!--Twitter for Mac-->
|
||||
<item>Twitter for Mac</item>
|
||||
<!--Twitter for Windows Phone-->
|
||||
<item>Twitter for Windows Phone</item>
|
||||
<!--Twitter for Google TV-->
|
||||
<item>Twitter for Google TV</item>
|
||||
<!--TweetDeck-->
|
||||
<item>TweetDeck</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="types_official_consumer_secret">
|
||||
<!--Twitter for Android-->
|
||||
<item>TWITTER_FOR_ANDROID</item>
|
||||
<!--Twitter for iPhone-->
|
||||
<item>TWITTER_FOR_IPHONE</item>
|
||||
<!--Twitter for iPad-->
|
||||
<item>TWITTER_FOR_IPAD</item>
|
||||
<!--Twitter for Mac-->
|
||||
<item>TWITTER_FOR_MAC</item>
|
||||
<!--Twitter for Windows Phone-->
|
||||
<item>TWITTER_FOR_WINDOWS_PHONE</item>
|
||||
<!--Twitter for Google TV-->
|
||||
<item>TWITTER_FOR_GOOGLE_TV</item>
|
||||
<!--TweetDeck-->
|
||||
<item>TWEETDECK</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="value_image_sources">
|
||||
<item>camera</item>
|
||||
<item>gallery</item>
|
||||
|
@ -99,23 +65,6 @@
|
|||
<item>mentions</item>
|
||||
<item>inbox</item>
|
||||
</string-array>
|
||||
<!-- CRC32 checksum of consumer secret of official clients to check whether user is using official keys -->
|
||||
<string-array name="values_official_consumer_secret_crc32">
|
||||
<!--Twitter for Android-->
|
||||
<item>6ce85096</item>
|
||||
<!--Twitter for iPhone-->
|
||||
<item>deffe9c7</item>
|
||||
<!--Twitter for iPad-->
|
||||
<item>9f00e0cb</item>
|
||||
<!--Twitter for Mac-->
|
||||
<item>df27640e</item>
|
||||
<!--Twitter for Windows Phone-->
|
||||
<item>62bd0d33</item>
|
||||
<!--Twitter for Google TV-->
|
||||
<item>56d8f9ff</item>
|
||||
<!--TweetDeck-->
|
||||
<item>ac602936</item>
|
||||
</string-array>
|
||||
<string-array name="values_profile_image_style">
|
||||
<item>round</item>
|
||||
<item>square</item>
|
||||
|
|
Loading…
Reference in New Issue