fixed hide text in media dm

improved api request
This commit is contained in:
Mariotaku Lee 2017-02-25 12:06:04 +08:00
parent b9e97e698c
commit e0d192c2e3
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
14 changed files with 202 additions and 131 deletions

View File

@ -29,18 +29,16 @@ 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.KeyValue;
import org.mariotaku.restfu.annotation.param.Param;
import org.mariotaku.restfu.annotation.param.Path;
import org.mariotaku.restfu.annotation.param.Queries;
import org.mariotaku.restfu.annotation.param.Query;
import org.mariotaku.restfu.http.BodyType;
@Queries({@KeyValue(key = "include_groups", value = "true"),
@KeyValue(key = "include_conversation_info", value = "true"),
@KeyValue(key = "ext", value = "stickerInfo,mediaRestrictions,altText")})
@Queries(template = DMAnnotationTemplate.class)
public interface PrivateDirectMessagesResources extends PrivateResources {
@POST("/dm/conversation/{conversation_id}/delete.json")
@ -53,6 +51,10 @@ public interface PrivateDirectMessagesResources extends PrivateResources {
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,

View File

@ -25,6 +25,7 @@ package org.mariotaku.microblog.library.twitter.model;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
@ -494,7 +495,7 @@ public class User extends TwitterResponseObject implements Comparable<User>, Par
return defaultProfileImage;
}
@Nullable
public Date getCreatedAt() {
return createdAt;
}

View File

@ -0,0 +1,43 @@
/*
* Twidere - Twitter client for Android
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.template;
import org.mariotaku.restfu.annotation.param.KeyValue;
import org.mariotaku.restfu.annotation.param.Queries;
/**
* Created by mariotaku on 16/5/27.
*/
@Queries({@KeyValue(key = "include_entities", valueKey = "include_entities"),
@KeyValue(key = "include_cards", valueKey = "include_cards"),
@KeyValue(key = "cards_platform", valueKey = "cards_platform"),
@KeyValue(key = "include_ext_alt_text", valueKey = "include_ext_alt_text"),
@KeyValue(key = "tweet_mode", valueKey = "tweet_mode"),
@KeyValue(key = "include_groups", value = "true"),
@KeyValue(key = "dm_users", value = "true"),
@KeyValue(key = "include_conversation_info", value = "true"),
@KeyValue(key = "ext", value = "stickerInfo,mediaRestrictions,altText"),
@KeyValue(key = "include_blocking", value = "true"),
@KeyValue(key = "include_blocked_by", value = "true")
})
public class DMAnnotationTemplate {
}

View File

@ -0,0 +1,59 @@
/*
* 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.util.api
import android.os.Build
import org.mariotaku.ktextension.bcp47Tag
import org.mariotaku.twidere.util.MicroBlogAPIFactory.ExtraHeaders
import java.util.*
/**
* Created by mariotaku on 2017/2/25.
*/
class TwitterAndroidExtraHeaders : ExtraHeaders {
override fun get(): List<Pair<String, String>> {
val result = ArrayList<Pair<String, String>>()
val language = Locale.getDefault().bcp47Tag
result.add(Pair("User-Agent", userAgent))
result.add(Pair("Accept-Language", language))
result.add(Pair("X-Twitter-Client", clientName))
result.add(Pair("X-Twitter-Client-Language", language))
result.add(Pair("X-Twitter-Client-Version", versionName))
result.add(Pair("X-Twitter-API-Version", apiVersion))
return result
}
val userAgent: String get() {
val model = Build.MODEL
val manufacturer = Build.MANUFACTURER
val sdkRelease = Build.VERSION.RELEASE
val brand = Build.BRAND
val product = Build.PRODUCT
return "$clientName/$versionName ($internalVersionName) $model/$sdkRelease ($manufacturer;$model;$brand;$product;0;;0)"
}
companion object {
const val clientName = "TwitterAndroid"
const val versionName = "6.35.0"
const val apiVersion = "5"
const val internalVersionName = "6160050-r-918"
}
}

View File

@ -0,0 +1,33 @@
/*
* 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.util.api
import org.mariotaku.twidere.util.MicroBlogAPIFactory
/**
* Created by mariotaku on 2017/2/25.
*/
class UserAgentExtraHeaders(val userAgent: String?) : MicroBlogAPIFactory.ExtraHeaders {
override fun get(): List<Pair<String, String>> {
if (userAgent == null) return emptyList()
return listOf(Pair("User-Agent", userAgent))
}
}

View File

@ -5,7 +5,6 @@ import android.accounts.AccountManager;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
@ -35,7 +34,6 @@ import org.mariotaku.restfu.http.ValueMap;
import org.mariotaku.restfu.http.mime.Body;
import org.mariotaku.restfu.oauth.OAuthEndpoint;
import org.mariotaku.restfu.oauth.OAuthToken;
import org.mariotaku.twidere.BuildConfig;
import org.mariotaku.twidere.TwidereConstants;
import org.mariotaku.twidere.annotation.AccountType;
import org.mariotaku.twidere.extension.model.AccountExtensionsKt;
@ -44,14 +42,17 @@ import org.mariotaku.twidere.model.ConsumerKeyType;
import org.mariotaku.twidere.model.UserKey;
import org.mariotaku.twidere.model.account.cred.Credentials;
import org.mariotaku.twidere.model.util.AccountUtils;
import org.mariotaku.twidere.util.api.TwitterAndroidExtraHeaders;
import org.mariotaku.twidere.util.api.UserAgentExtraHeaders;
import java.io.IOException;
import java.util.HashMap;
import java.util.Locale;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import kotlin.Pair;
import okhttp3.HttpUrl;
/**
@ -115,7 +116,6 @@ public class MicroBlogAPIFactory implements TwidereConstants {
}
case AccountType.TWITTER: {
extraParams.put("include_entities", String.valueOf(includeEntities));
extraParams.put("include_retweets", String.valueOf(includeRetweets));
break;
}
}
@ -192,37 +192,26 @@ public class MicroBlogAPIFactory implements TwidereConstants {
}
@WorkerThread
public static String getUserAgentName(Context context, ConsumerKeyType type) {
@Nullable
public static ExtraHeaders getExtraHeaders(Context context, ConsumerKeyType type) {
switch (type) {
case TWITTER_FOR_ANDROID: {
final String versionName = "6.3.4";
final String internalVersionName = "6110049-r-917";
final String model = Build.MODEL;
final String manufacturer = Build.MANUFACTURER;
final int sdkInt = Build.VERSION.SDK_INT;
final String sdkRelease = Build.VERSION.RELEASE;
final String device = Build.DEVICE;
final String brand = Build.BRAND;
final String product = Build.PRODUCT;
final int debug = BuildConfig.DEBUG ? 1 : 0;
return String.format(Locale.US, "TwitterAndroid /%s (%s) %s/%s (%s;%s;%s;%s;%d)",
versionName, internalVersionName, model, sdkRelease, manufacturer, model, brand,
product, debug);
return new TwitterAndroidExtraHeaders();
}
case TWITTER_FOR_IPHONE: {
return "Twitter-iPhone";
return new UserAgentExtraHeaders("Twitter-iPhone");
}
case TWITTER_FOR_IPAD: {
return "Twitter-iPad";
return new UserAgentExtraHeaders("Twitter-iPad");
}
case TWITTER_FOR_MAC: {
return "Twitter-Mac";
return new UserAgentExtraHeaders("Twitter-Mac");
}
case TWEETDECK: {
return UserAgentUtils.getDefaultUserAgentStringSafe(context);
return new UserAgentExtraHeaders(UserAgentUtils.getDefaultUserAgentStringSafe(context));
}
}
return "Twitter";
return null;
}
public static String getTwidereUserAgent(final Context context) {
@ -290,10 +279,11 @@ public class MicroBlogAPIFactory implements TwidereConstants {
public static class TwidereHttpRequestFactory implements HttpRequest.Factory {
private final String userAgent;
@Nullable
private final ExtraHeaders extraHeaders;
public TwidereHttpRequestFactory(final String userAgent) {
this.userAgent = userAgent;
public TwidereHttpRequestFactory(final @Nullable ExtraHeaders extraHeaders) {
this.extraHeaders = extraHeaders;
}
@Override
@ -311,7 +301,11 @@ public class MicroBlogAPIFactory implements TwidereConstants {
if (authorization != null && authorization.hasAuthorization()) {
headers.add("Authorization", RestFuUtils.sanitizeHeader(authorization.getHeader(endpoint, info)));
}
headers.add("User-Agent", RestFuUtils.sanitizeHeader(userAgent));
if (extraHeaders != null) {
for (final Pair<String, String> pair : extraHeaders.get()) {
headers.add(pair.getFirst(), RestFuUtils.sanitizeHeader(pair.getSecond()));
}
}
return new HttpRequest(restMethod, url, headers, info.getBody(converterFactory), null);
}
}
@ -384,4 +378,9 @@ public class MicroBlogAPIFactory implements TwidereConstants {
params, rawValue, bodyType, extras);
}
}
public interface ExtraHeaders {
@NonNull
List<Pair<String, String>> get();
}
}

View File

@ -1,71 +0,0 @@
/*
* Twidere - Twitter client for Android
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.model
import org.mariotaku.microblog.library.MicroBlog
import org.mariotaku.microblog.library.MicroBlogException
import org.mariotaku.microblog.library.twitter.model.DMResponse.Entry.Message
/**
* I don't know why Twitter doesn't return video/animatedGif when requesting DM, but this will help
*/
fun DMResponse.fixMedia(microBlog: MicroBlog) {
entries?.forEach { entry ->
// Ensure it's a normal message
val data = entry.message?.messageData ?: return@forEach
// Ensure this message don't have attachment
if (data.attachment != null) return@forEach
// Don't try if it's a group dm
if (conversations?.get(entry.message.conversationId)?.type == DMResponse.Conversation.Type.GROUP_DM) {
return@forEach
}
val mediaUrl = "https://twitter.com/messages/media/${data.id}"
if (data.entities?.urls?.find { it.expandedUrl == mediaUrl } == null) return@forEach
val message = try {
microBlog.showDirectMessage(data.id)
} catch (e: MicroBlogException) {
// Ignore
return@forEach
}
val media = message.entities?.media?.find { it.expandedUrl == mediaUrl } ?: return@forEach
when (media.type) {
MediaEntity.Type.VIDEO -> {
data.attachment = attachment { video = media }
}
MediaEntity.Type.PHOTO -> {
data.attachment = attachment { photo = media }
}
MediaEntity.Type.ANIMATED_GIF -> {
data.attachment = attachment { animatedGif = media }
}
}
}
}
private inline fun attachment(apply: Message.Data.Attachment.() -> Unit): Message.Data.Attachment {
val attachment = Message.Data.Attachment()
apply(attachment)
return attachment
}

View File

@ -16,7 +16,6 @@ import org.mariotaku.restfu.oauth.OAuthEndpoint
import org.mariotaku.restfu.oauth.OAuthToken
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.BasicCredentials
import org.mariotaku.twidere.model.account.cred.Credentials
import org.mariotaku.twidere.model.account.cred.EmptyCredentials
@ -26,6 +25,7 @@ import org.mariotaku.twidere.util.MicroBlogAPIFactory
import org.mariotaku.twidere.util.MicroBlogAPIFactory.sFanfouConstantPool
import org.mariotaku.twidere.util.MicroBlogAPIFactory.sTwitterConstantPool
import org.mariotaku.twidere.util.TwitterContentUtils
import org.mariotaku.twidere.util.api.UserAgentExtraHeaders
import org.mariotaku.twidere.util.dagger.DependencyHolder
/**
@ -111,17 +111,12 @@ fun <T> newMicroBlogInstance(context: Context, endpoint: Endpoint, auth: Authori
@AccountType accountType: String? = null, extraRequestParams: Map<String, String>? = null,
cls: Class<T>): T {
val factory = RestAPIFactory<MicroBlogException>()
val userAgent: String
if (auth is OAuthAuthorization) {
val extraHeaders = if (auth is OAuthAuthorization) {
val officialKeyType = TwitterContentUtils.getOfficialKeyType(context,
auth.consumerKey, auth.consumerSecret)
if (officialKeyType != ConsumerKeyType.UNKNOWN) {
userAgent = MicroBlogAPIFactory.getUserAgentName(context, officialKeyType)
} else {
userAgent = MicroBlogAPIFactory.getTwidereUserAgent(context)
}
MicroBlogAPIFactory.getExtraHeaders(context, officialKeyType)
} else {
userAgent = MicroBlogAPIFactory.getTwidereUserAgent(context)
UserAgentExtraHeaders(MicroBlogAPIFactory.getTwidereUserAgent(context))
}
val holder = DependencyHolder.get(context)
when (cls) {
@ -152,7 +147,7 @@ fun <T> newMicroBlogInstance(context: Context, endpoint: Endpoint, auth: Authori
val converterFactory = TwitterConverterFactory()
factory.setRestConverterFactory(converterFactory)
factory.setRestRequestFactory(MicroBlogAPIFactory.TwidereRestRequestFactory(extraRequestParams))
factory.setHttpRequestFactory(MicroBlogAPIFactory.TwidereHttpRequestFactory(userAgent))
factory.setHttpRequestFactory(MicroBlogAPIFactory.TwidereHttpRequestFactory(extraHeaders))
factory.setExceptionFactory(MicroBlogAPIFactory.TwidereExceptionFactory(converterFactory))
return factory.build<T>(cls)
}

View File

@ -489,11 +489,17 @@ class UserFragment : BaseFragment(), OnClickListener, OnLinkClickListener,
locationContainer.location.text = user.location
urlContainer.visibility = if (TextUtils.isEmpty(user.url) && TextUtils.isEmpty(user.url_expanded)) View.GONE else View.VISIBLE
urlContainer.url.text = if (TextUtils.isEmpty(user.url_expanded)) user.url else user.url_expanded
val createdAt = Utils.formatToLongTimeString(activity, user.created_at)
val daysSinceCreation = (System.currentTimeMillis() - user.created_at) / 1000 / 60 / 60 / 24.toFloat()
val dailyTweets = Math.round(user.statuses_count / Math.max(1f, daysSinceCreation))
createdAtContainer.createdAt.text = resources.getQuantityString(R.plurals.created_at_with_N_tweets_per_day, dailyTweets,
createdAt, dailyTweets)
if (user.created_at >= 0) {
val createdAt = Utils.formatToLongTimeString(activity, user.created_at)
val daysSinceCreation = (System.currentTimeMillis() - user.created_at) / 1000 / 60 / 60 / 24.toFloat()
val dailyTweets = Math.round(user.statuses_count / Math.max(1f, daysSinceCreation))
createdAtContainer.visibility = View.VISIBLE
createdAtContainer.createdAt.text = resources.getQuantityString(R.plurals.created_at_with_N_tweets_per_day, dailyTweets,
createdAt, dailyTweets)
} else {
createdAtContainer.visibility = View.GONE
}
listedContainer.listedCount.text = Utils.getLocalizedNumber(locale, user.listed_count)
val groupsCount = if (user.extras != null) user.extras.groups_count else -1
groupsContainer.groupsCount.text = Utils.getLocalizedNumber(locale, groupsCount)
@ -526,13 +532,17 @@ class UserFragment : BaseFragment(), OnClickListener, OnLinkClickListener,
activity.title = UserColorNameManager.decideDisplayName(user.nickname, user.name,
user.screen_name, nameFirst)
val cal = Calendar.getInstance()
val currentMonth = cal.get(Calendar.MONTH)
val currentDay = cal.get(Calendar.DAY_OF_MONTH)
cal.timeInMillis = user.created_at
val userCreationDay = condition@ if (user.created_at >= 0) {
val cal = Calendar.getInstance()
val currentMonth = cal.get(Calendar.MONTH)
val currentDay = cal.get(Calendar.DAY_OF_MONTH)
cal.timeInMillis = user.created_at
cal.get(Calendar.MONTH) == currentMonth && cal.get(Calendar.DAY_OF_MONTH) == currentDay
} else {
false
}
val twitterversary = cal.get(Calendar.MONTH) == currentMonth && cal.get(Calendar.DAY_OF_MONTH) == currentDay
if ((BuildConfig.DEBUG || twitterversary) && !hideBirthdayView) {
if (userCreationDay && !hideBirthdayView) {
if (profileBirthdayStub != null) {
profileBirthdayBanner = profileBirthdayStub.inflate()
profileBirthdayBanner.setOnClickListener(this)

View File

@ -190,6 +190,11 @@ object ParcelableMessageUtils {
val media = arrayOf(ParcelableMediaUtils.fromMediaEntity(video))
return Triple(MessageType.TEXT, null, media)
}
attachment.animatedGif != null -> {
val video = attachment.animatedGif
val media = arrayOf(ParcelableMediaUtils.fromMediaEntity(video))
return Triple(MessageType.TEXT, null, media)
}
attachment.sticker != null -> {
val sticker = attachment.sticker
val image = sticker.images["size_2x"] ?: sticker.images.values.firstOrNull() ?:

View File

@ -26,7 +26,7 @@ object ParcelableUserUtils {
obj.position = position
obj.account_key = accountKey
obj.key = UserKeyUtils.fromUser(user)
obj.created_at = user.createdAt.time
obj.created_at = user.createdAt?.time ?: -1
obj.is_protected = user.isProtected
obj.is_verified = user.isVerified
obj.name = user.name

View File

@ -30,7 +30,6 @@ import org.mariotaku.microblog.library.MicroBlogException
import org.mariotaku.microblog.library.twitter.model.DMResponse
import org.mariotaku.microblog.library.twitter.model.Paging
import org.mariotaku.microblog.library.twitter.model.User
import org.mariotaku.microblog.library.twitter.model.fixMedia
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.twidere.TwidereConstants.QUERY_PARAM_NOTIFY
import org.mariotaku.twidere.annotation.AccountType
@ -185,7 +184,6 @@ class GetMessagesTask(
}
val response = microBlog.getDmConversation(conversationId, paging).conversationTimeline
response.fixMedia(microBlog)
return Companion.createDatabaseUpdateData(context, details, response)
}
@ -202,7 +200,6 @@ class GetMessagesTask(
}
}).userInbox
}
response.fixMedia(microBlog)
return Companion.createDatabaseUpdateData(context, details, response)
}

View File

@ -26,7 +26,6 @@ import org.mariotaku.microblog.library.MicroBlogException
import org.mariotaku.microblog.library.twitter.TwitterUpload
import org.mariotaku.microblog.library.twitter.model.DirectMessage
import org.mariotaku.microblog.library.twitter.model.NewDm
import org.mariotaku.microblog.library.twitter.model.fixMedia
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.twidere.annotation.AccountType
import org.mariotaku.twidere.extension.model.isOfficial
@ -121,7 +120,6 @@ class SendMessageTask(
it.message != null
}?.message?.conversationId
val response = microBlog.getDmConversation(conversationId, null).conversationTimeline
response.fixMedia(microBlog)
return GetMessagesTask.createDatabaseUpdateData(context, account, response)
}

View File

@ -78,7 +78,7 @@ class MessageViewHolder(itemView: View, adapter: MessagesConversationAdapter) :
var nonSpaceCount = 0
var curPos = 0
message.spans?.forEach { span ->
nonSpaceCount += text.nonSpaceCount(curPos..span.start)
nonSpaceCount += text.nonSpaceCount(curPos..span.start - 1)
if (message.media?.firstOrNull { media -> span.link == media.url } != null) {
// Skip if span is hidden
span.type = SpanItem.SpanType.HIDE