1
0
mirror of https://github.com/TwidereProject/Twidere-Android synced 2025-02-17 04:00:48 +01:00

implemented algorithm to revive good old mentions

This commit is contained in:
Mariotaku Lee 2017-04-02 13:58:04 +08:00
parent b495048f89
commit ec6ba7b4e0
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
17 changed files with 418 additions and 150 deletions

View File

@ -68,6 +68,11 @@ public class StatusUpdate extends SimpleValueMap {
return this;
}
public StatusUpdate excludeReplyUserIds(final String[] ids) {
put("exclude_reply_userids", RestFuUtils.toString(ids, ','));
return this;
}
public StatusUpdate location(final GeoLocation location) {
remove("lat");
remove("long");

View File

@ -1,49 +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.extension
import com.twitter.Validator
import org.junit.Assert
import org.junit.Test
/**
* Created by mariotaku on 2017/3/31.
*/
class ValidatorExtensionsKtTest {
val validator = Validator()
@Test
fun getTweetLength() {
Assert.assertEquals(0, validator.getTweetLength("@user1 ", true))
Assert.assertEquals(0, validator.getTweetLength("@user1 ", true))
Assert.assertEquals(4, validator.getTweetLength("@user1 @user2 test", true))
Assert.assertEquals(4, validator.getTweetLength("@user1 @user2 test", true))
Assert.assertEquals(4, validator.getTweetLength("@user1 @user2 test", true))
Assert.assertEquals(9, validator.getTweetLength("@user1 @user2 test ", true))
Assert.assertEquals(11, validator.getTweetLength("@user1 test @user2", true))
Assert.assertEquals(4, validator.getTweetLength("test", true))
val long50Mentions = Array(50) { "@user${it + 1}" }.joinToString(" ")
Assert.assertEquals(4, validator.getTweetLength("$long50Mentions test", true))
Assert.assertEquals(12, validator.getTweetLength("$long50Mentions @user51 test", true))
}
}

View File

@ -0,0 +1,157 @@
/*
* 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.extension.text.twitter
import android.support.test.InstrumentationRegistry
import android.support.test.runner.AndroidJUnit4
import com.bluelinelabs.logansquare.LoganSquare
import com.twitter.Extractor
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mariotaku.twidere.model.ParcelableStatus
import org.mariotaku.twidere.model.ParcelableUserMention
import org.mariotaku.twidere.test.R
/**
* Created by mariotaku on 2017/4/2.
*/
@RunWith(AndroidJUnit4::class)
class ExtractorExtensionsKtTest {
private val extractor = Extractor()
private lateinit var inReplyTo: ParcelableStatus
@Before
fun setUp() {
val context = InstrumentationRegistry.getContext()
// This is a tweet by @t_deyarmin, mentioning @nixcraft
inReplyTo = context.resources.openRawResource(R.raw.parcelable_status_848051071444410368).use {
LoganSquare.parse(it, ParcelableStatus::class.java)
}
}
@Test
fun testExtractReplyTextAndMentionsReplyToAll() {
// This reply to all users, which is the normal case
extractor.extractReplyTextAndMentions("@t_deyarmin @nixcraft lol", inReplyTo).let {
Assert.assertEquals("lol", it.replyText)
Assert.assertTrue("extraMentions.isEmpty()", it.extraMentions.isEmpty())
Assert.assertTrue("excludedMentions.isEmpty()", it.excludedMentions.isEmpty())
}
}
@Test
fun testExtractReplyTextAndMentionsReplyToAllExtraMentions() {
// This reply to all users, which is the normal case
extractor.extractReplyTextAndMentions("@t_deyarmin @nixcraft @mariotaku lol", inReplyTo).let {
Assert.assertEquals("@mariotaku lol", it.replyText)
Assert.assertTrue("extraMentions.isEmpty()", it.extraMentions.entitiesContainsAll("mariotaku"))
Assert.assertTrue("excludedMentions.isEmpty()", it.excludedMentions.isEmpty())
}
}
@Test
fun testExtractReplyTextAndMentionsReplyToAllSuffixMentions() {
// This reply to all users, which is the normal case
extractor.extractReplyTextAndMentions("@t_deyarmin @nixcraft lol @mariotaku", inReplyTo).let {
Assert.assertEquals("lol @mariotaku", it.replyText)
Assert.assertTrue("extraMentions.isEmpty()", it.extraMentions.entitiesContainsAll("mariotaku"))
Assert.assertTrue("excludedMentions.isEmpty()", it.excludedMentions.isEmpty())
}
}
@Test
fun testExtractReplyTextAndMentionsAuthorOnly() {
// This reply removed @nixcraft and replying to author only
extractor.extractReplyTextAndMentions("@t_deyarmin lol", inReplyTo).let {
Assert.assertEquals("lol", it.replyText)
Assert.assertTrue("extraMentions.isEmpty()", it.extraMentions.isEmpty())
Assert.assertTrue("excludedMentions.containsAll(expectation)",
it.excludedMentions.mentionsContainsAll("nixcraft"))
}
}
@Test
fun testExtractReplyTextAndMentionsAuthorOnlyExtraMentions() {
// This reply removed @nixcraft and replying to author only
extractor.extractReplyTextAndMentions("@t_deyarmin @mariotaku lol", inReplyTo).let {
Assert.assertEquals("@mariotaku lol", it.replyText)
val extraExpectation = setOf("mariotaku")
Assert.assertTrue("extraMentions.containsAll(expectation)", it.extraMentions.all {
it.value in extraExpectation
})
Assert.assertTrue("excludedMentions.containsAll(expectation)",
it.excludedMentions.mentionsContainsAll("nixcraft"))
}
}
@Test
fun testExtractReplyTextAndMentionsAuthorOnlySuffixMention() {
// This reply removed @nixcraft and replying to author only
extractor.extractReplyTextAndMentions("@t_deyarmin lol @mariotaku", inReplyTo).let {
Assert.assertEquals("lol @mariotaku", it.replyText)
Assert.assertTrue("extraMentions.isEmpty()",
it.extraMentions.entitiesContainsAll("mariotaku"))
Assert.assertTrue("excludedMentions.containsAll(expectation)",
it.excludedMentions.mentionsContainsAll("nixcraft"))
}
}
@Test
fun testExtractReplyTextAndMentionsNoAuthor() {
// This reply removed author @t_deyarmin
extractor.extractReplyTextAndMentions("@nixcraft lol", inReplyTo).let {
Assert.assertEquals("@nixcraft lol", it.replyText)
Assert.assertTrue("extraMentions.isEmpty()", it.extraMentions.isEmpty())
Assert.assertTrue("excludedMentions.isEmpty()", it.excludedMentions.isEmpty())
}
}
@Test
fun testExtractReplyTextAndMentionsNoAuthorExtraMentions() {
// This reply removed author @t_deyarmin
extractor.extractReplyTextAndMentions("@nixcraft @mariotaku lol", inReplyTo).let {
Assert.assertEquals("@nixcraft @mariotaku lol", it.replyText)
Assert.assertTrue("extraMentions.containsAll(expectation)",
it.extraMentions.entitiesContainsAll("mariotaku"))
Assert.assertTrue("excludedMentions.isEmpty()", it.excludedMentions.isEmpty())
}
}
private fun List<Extractor.Entity>.entitiesContainsAll(vararg screenNames: String): Boolean {
return all { entity ->
screenNames.any { expectation ->
expectation.equals(entity.value, ignoreCase = true)
}
}
}
private fun List<ParcelableUserMention>.mentionsContainsAll(vararg screenNames: String): Boolean {
return all { mention ->
screenNames.any { expectation ->
expectation.equals(mention.screen_name, ignoreCase = true)
}
}
}
}

View File

@ -1,5 +0,0 @@
package org.mariotaku.twidere.model
/**
* Created by mariotaku on 2016/12/7.
*/

View File

@ -28,7 +28,7 @@ class StatusShortenerInterfaceTest {
val application = context.applicationContext as Application
val preferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
val shortenerComponent = preferences.getString(TwidereConstants.KEY_STATUS_SHORTENER, null) ?: return
val instance = StatusShortenerInterface.getInstance(application, shortenerComponent)
val instance = StatusShortenerInterface.getInstance(application, shortenerComponent) ?: return
instance.checkService { metaData ->
if (metaData == null) throw UpdateStatusTask.ExtensionVersionMismatchException()
val extensionVersion = metaData.getString(TwidereConstants.METADATA_KEY_EXTENSION_VERSION_STATUS_SHORTENER)

View File

@ -0,0 +1,66 @@
{
"account_color": -16537100,
"account_id": "57610574@twitter.com",
"extras": {
"conversation_id": "848051071444410368",
"display_text_range": [0, 37],
"support_entities": true,
"user_profile_image_url_fallback": "https://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png"
},
"favorite_count": 103,
"id": "848051071444410368",
"in_reply_to_user_id": null,
"is_favorite": false,
"is_gap": false,
"is_possibly_sensitive": false,
"is_quote": false,
"is_retweet": true,
"lang": "en",
"media": [{
"height": 1152,
"media_url": "https://pbs.twimg.com/media/C8SGqMIUwAArTQF.jpg",
"open_browser": false,
"page_url": "https://twitter.com/t_deyarmin/status/847950697987493888/photo/1",
"preview_url": "https://pbs.twimg.com/media/C8SGqMIUwAArTQF.jpg",
"type": 1,
"url": "https://pbs.twimg.com/media/C8SGqMIUwAArTQF.jpg",
"width": 2048
}],
"mentions": [{
"id": "17484680@twitter.com",
"name": "touch /var/www/html",
"screen_name": "nixcraft"
}],
"my_retweet_id": "848051071444410368",
"position_key": 1491026106404,
"quoted_timestamp": 0,
"quoted_user_is_protected": false,
"quoted_user_is_verified": false,
"quoted_user_id": null,
"reply_count": 13,
"retweet_count": 53,
"retweet_id": "847950697987493888",
"retweet_timestamp": 1491002175000,
"retweeted": true,
"retweeted_by_user_id": "57610574@twitter.com",
"retweeted_by_user_name": "宅撩一边捣鼓着Twidere一边",
"retweeted_by_user_profile_image": "https://pbs.twimg.com/profile_images/815085525035909124/2XX_ScDQ_reasonably_small.jpg",
"retweeted_by_user_screen_name": "mariotaku",
"sort_id": 848051071444410368,
"source": "<a href=\"http://twitter.com/download/iphone\" rel=\"nofollow\">Twitter for iPhone</a>",
"spans": [{
"end": 64,
"link": "https://twitter.com/t_deyarmin/status/847950697987493888/photo/1",
"start": 38
}],
"text_plain": "What OS am I running again? @nixcraft https://t.co/acmtDZWyhp",
"text_unescaped": "What OS am I running again? @nixcraft pic.twitter.com/acmtDZWyhp",
"timestamp": 1491026106000,
"user_is_following": false,
"user_is_protected": false,
"user_is_verified": false,
"user_id": "2957592417@twitter.com",
"user_name": "Travis Deyarmin",
"user_profile_image_url": "https://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png",
"user_screen_name": "t_deyarmin"
}

View File

@ -27,6 +27,7 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.IBinder;
import android.support.annotation.Nullable;
import org.mariotaku.twidere.IMediaUploader;
import org.mariotaku.twidere.model.MediaUploadResult;
@ -40,13 +41,14 @@ import java.util.List;
import static org.mariotaku.twidere.constant.IntentConstants.INTENT_ACTION_EXTENSION_UPLOAD_MEDIA;
public final class MediaUploaderInterface extends AbsServiceInterface<IMediaUploader> {
protected MediaUploaderInterface(Context context, String uploaderName, Bundle metaData) {
private MediaUploaderInterface(Context context, String uploaderName, Bundle metaData) {
super(context, uploaderName, metaData);
}
public MediaUploadResult upload(final ParcelableStatusUpdate status,
final UserKey currentAccountKey,
final UploaderMediaItem[] media) {
final UserKey currentAccountKey,
final UploaderMediaItem[] media) {
final IMediaUploader iface = getInterface();
if (iface == null) return MediaUploadResult.error(1, "Uploader not ready");
try {
@ -77,6 +79,7 @@ public final class MediaUploaderInterface extends AbsServiceInterface<IMediaUplo
return IMediaUploader.Stub.asInterface(obj);
}
@Nullable
public static MediaUploaderInterface getInstance(final Application application, final String uploaderName) {
if (uploaderName == null) return null;
final Intent intent = new Intent(INTENT_ACTION_EXTENSION_UPLOAD_MEDIA);

View File

@ -27,6 +27,7 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.IBinder;
import android.support.annotation.Nullable;
import org.mariotaku.twidere.IStatusShortener;
import org.mariotaku.twidere.model.ParcelableStatus;
@ -40,7 +41,7 @@ import static org.mariotaku.twidere.constant.IntentConstants.INTENT_ACTION_EXTEN
public final class StatusShortenerInterface extends AbsServiceInterface<IStatusShortener> {
protected StatusShortenerInterface(Context context, String shortenerName, Bundle metaData) {
private StatusShortenerInterface(Context context, String shortenerName, Bundle metaData) {
super(context, shortenerName, metaData);
}
@ -50,8 +51,8 @@ public final class StatusShortenerInterface extends AbsServiceInterface<IStatusS
}
public StatusShortenResult shorten(final ParcelableStatusUpdate status,
final UserKey currentAccountId,
final String overrideStatusText) {
final UserKey currentAccountId,
final String overrideStatusText) {
final IStatusShortener iface = getInterface();
if (iface == null) return StatusShortenResult.error(1, "Shortener not ready");
try {
@ -76,6 +77,7 @@ public final class StatusShortenerInterface extends AbsServiceInterface<IStatusS
}
}
@Nullable
public static StatusShortenerInterface getInstance(final Application application, final String shortenerName) {
if (shortenerName == null) return null;
final Intent intent = new Intent(INTENT_ACTION_EXTENSION_SHORTEN_STATUS);

View File

@ -78,12 +78,12 @@ import org.mariotaku.twidere.annotation.AccountType
import org.mariotaku.twidere.constant.*
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_SCREEN_NAME
import org.mariotaku.twidere.extension.applyTheme
import org.mariotaku.twidere.extension.getTweetLength
import org.mariotaku.twidere.extension.loadProfileImage
import org.mariotaku.twidere.extension.model.getAccountType
import org.mariotaku.twidere.extension.model.getAccountUser
import org.mariotaku.twidere.extension.model.textLimit
import org.mariotaku.twidere.extension.model.unique_id_non_null
import org.mariotaku.twidere.extension.text.twitter.getTweetLength
import org.mariotaku.twidere.fragment.*
import org.mariotaku.twidere.fragment.PermissionRequestDialog.PermissionRequestCancelCallback
import org.mariotaku.twidere.model.*
@ -1441,13 +1441,12 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
val accountKeys = accountsAdapter.selectedAccountKeys
val accounts = AccountUtils.getAllAccountDetails(AccountManager.get(this), accountKeys, true)
val ignoreMentions = accounts.all { it.type == AccountType.TWITTER }
val tweetLength = validator.getTweetLength(text, ignoreMentions &&
!defaultFeatures.isMentionsCountsInStatus)
val tweetLength = validator.getTweetLength(text, ignoreMentions, inReplyToStatus)
val maxLength = statusTextCount.maxLength
if (accountsAdapter.isSelectionEmpty) {
editText.error = getString(R.string.message_toast_no_account_selected)
return
} else if (!hasMedia && (TextUtils.isEmpty(text) || noReplyContent(text))) {
} else if (!hasMedia && (tweetLength <= 0 || noReplyContent(text))) {
editText.error = getString(R.string.error_message_no_content)
return
} else if (maxLength > 0 && !statusShortenerUsed && tweetLength > maxLength) {
@ -1502,8 +1501,8 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
val ignoreMentions = inReplyToStatus != null && accountsAdapter.selectedAccountKeys.all {
val account = AccountUtils.findByAccountKey(am, it) ?: return@all false
return@all account.getAccountType(am) == AccountType.TWITTER
} && !defaultFeatures.isMentionsCountsInStatus
statusTextCount.textCount = validator.getTweetLength(text, ignoreMentions)
}
statusTextCount.textCount = validator.getTweetLength(text, ignoreMentions, inReplyToStatus)
}
private fun updateUpdateStatusIcon() {

View File

@ -1,60 +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.extension
import com.twitter.Extractor
import com.twitter.Validator
import java.text.Normalizer
/**
* Created by mariotaku on 2017/3/31.
*/
fun Validator.getTweetLength(text: String, ignoreMentions: Boolean): Int {
var temp = text
temp = Normalizer.normalize(temp, Normalizer.Form.NFC)
var length = temp.codePointCount(0, temp.length)
if (ignoreMentions) {
var nextExpectedPos = 0
run {
val mentions = InternalExtractor.extractMentionedScreennamesWithIndices(temp)
mentions.forEachIndexed { index, entity ->
// Limit to 50 mentions https://dev.twitter.com/overview/api/upcoming-changes-to-tweets
if (index >= 50) return@run
if (entity.start != nextExpectedPos) return@run
nextExpectedPos = (entity.end..temp.indices.endInclusive).firstOrNull {
!temp[it].isWhitespace()
} ?: temp.indices.endInclusive + 1
}
}
length -= temp.codePointCount(0, nextExpectedPos)
}
for (urlEntity in InternalExtractor.extractURLsWithIndices(temp)) {
length += urlEntity.start - urlEntity.end
length += if (urlEntity.value.startsWith("https://", ignoreCase = true)) shortUrlLengthHttps else shortUrlLength
}
return length
}
private object InternalExtractor : Extractor()

View File

@ -0,0 +1,90 @@
/*
* 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.extension.text.twitter
import com.twitter.Extractor
import org.mariotaku.twidere.model.ParcelableStatus
import org.mariotaku.twidere.model.ParcelableUserMention
fun Extractor.extractMentionsAndNonMentionStartIndex(text: String): MentionsAndNonMentionStartIndex {
var nextExpectedPos = 0
val mentions = extractMentionedScreennamesWithIndices(text)
@Suppress("LoopToCallChain")
for (entity in mentions) {
if (entity.start != nextExpectedPos) break
nextExpectedPos = (entity.end..text.indices.endInclusive).firstOrNull {
!text[it].isWhitespace()
} ?: text.indices.endInclusive + 1
}
return MentionsAndNonMentionStartIndex(mentions, nextExpectedPos)
}
fun Extractor.extractReplyTextAndMentions(text: String, inReplyTo: ParcelableStatus): ReplyTextAndMentions {
// First extract mentions and 'real text' start index
val (mentions, index) = extractMentionsAndNonMentionStartIndex(text)
// Find mentions that `inReplyTo` doesn't have and add to `extraMentions` list
val extraMentions = mentions.filter { entity ->
if (entity.value.equals(inReplyTo.user_screen_name, ignoreCase = true)) {
return@filter false
}
return@filter inReplyTo.mentions?.none { mention ->
entity.value.equals(mention.screen_name, ignoreCase = true)
} ?: true
}
// Find removed mentions from `inReplyTo` and add to `excludedMentions` list
val excludedMentions = inReplyTo.mentions?.filter { mention ->
return@filter mentions.none { entity ->
entity.value.equals(mention.screen_name, ignoreCase = true)
}
}.orEmpty()
// Find reply text contains mention to `inReplyTo.user`
val mentioningUser = mentions.any {
it.value.equals(inReplyTo.user_screen_name, ignoreCase = true)
}
if (!mentioningUser) {
/*
* remember to process status without mentioning user of `inReplyTo`
* then this status should be treated at a mention referring to `inReplyTo`, all other mentions
* counts.
*/
return ReplyTextAndMentions(text, emptyList(), emptyList())
}
val overrideText = run {
val sb = StringBuilder()
extraMentions.forEach { entity ->
if (entity.start >= index) return@forEach
sb.append('@')
sb.append(entity.value)
sb.append(' ')
}
sb.append(text, index, text.length)
return@run sb.toString()
}
return ReplyTextAndMentions(overrideText, extraMentions, excludedMentions)
}
data class MentionsAndNonMentionStartIndex(val mentions: List<Extractor.Entity>, val index: Int)
data class ReplyTextAndMentions(
val replyText: String,
val extraMentions: List<Extractor.Entity>,
val excludedMentions: List<ParcelableUserMention>
)

View File

@ -0,0 +1,40 @@
/*
* 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.extension.text.twitter
import com.twitter.Extractor
import com.twitter.Validator
import org.mariotaku.twidere.model.ParcelableStatus
/**
* Created by mariotaku on 2017/3/31.
*/
fun Validator.getTweetLength(text: String, ignoreMentions: Boolean, inReplyTo: ParcelableStatus?): Int {
if (!ignoreMentions || inReplyTo == null) {
return getTweetLength(text)
}
val (replyText, _, _) = InternalExtractor.extractReplyTextAndMentions(text, inReplyTo)
return getTweetLength(replyText)
}
private object InternalExtractor : Extractor()

View File

@ -48,7 +48,6 @@ import org.mariotaku.twidere.annotation.AccountType
import org.mariotaku.twidere.constant.IntentConstants.*
import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_QUICK_SEND
import org.mariotaku.twidere.extension.applyTheme
import org.mariotaku.twidere.extension.getTweetLength
import org.mariotaku.twidere.extension.model.textLimit
import org.mariotaku.twidere.model.*
import org.mariotaku.twidere.model.util.AccountUtils
@ -199,7 +198,7 @@ class RetweetQuoteDialogFragment : BaseDialogFragment() {
}
val textCountView = dialog.findViewById(R.id.commentTextCount) as StatusTextCountView
val am = AccountManager.get(context)
textCountView.textCount = validator.getTweetLength(s.toString(), false)
textCountView.textCount = validator.getTweetLength(s.toString())
}
private val status: ParcelableStatus

View File

@ -185,7 +185,7 @@ class UserProfileEditorFragment : BaseFragment(), OnSizeChangedListener, TextWat
return
}
val lengthChecker = TwitterValidatorMETLengthChecker(Validator(), false)
val lengthChecker = TwitterValidatorMETLengthChecker(Validator())
editName.addTextChangedListener(this)
editDescription.addTextChangedListener(this)
editLocation.addTextChangedListener(this)

View File

@ -2,6 +2,7 @@ package org.mariotaku.twidere.task
import android.content.Context
import com.squareup.otto.Bus
import com.twitter.Extractor
import org.mariotaku.abstask.library.AbstractTask
import org.mariotaku.kpreferences.KPreferences
import org.mariotaku.twidere.model.DefaultFeatures
@ -42,6 +43,8 @@ abstract class BaseAbstractTask<Params, Result, Callback>(val context: Context)
lateinit var defaultFeatures: DefaultFeatures
@Inject
lateinit var scheduleProviderFactory: StatusScheduleProvider.Factory
@Inject
lateinit var extractor: Extractor
val scheduleProvider: StatusScheduleProvider?
get() = scheduleProviderFactory.newInstance(context)

View File

@ -37,10 +37,11 @@ import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.*
import org.mariotaku.twidere.annotation.AccountType
import org.mariotaku.twidere.app.TwidereApplication
import org.mariotaku.twidere.extension.getTweetLength
import org.mariotaku.twidere.extension.model.mediaSizeLimit
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
import org.mariotaku.twidere.extension.model.textLimit
import org.mariotaku.twidere.extension.text.twitter.extractReplyTextAndMentions
import org.mariotaku.twidere.extension.text.twitter.getTweetLength
import org.mariotaku.twidere.model.*
import org.mariotaku.twidere.model.account.AccountExtras
import org.mariotaku.twidere.model.analyzer.UpdateStatus
@ -111,6 +112,12 @@ class UpdateStatusTask(
val pendingUpdate = PendingStatusUpdate(update)
/*
* override status text, trim existing reply mentions, and add mentions that not
* exists in status text into ignore list
*/
regulateStatusText(update, pendingUpdate)
val result: UpdateStatusResult
try {
uploadMedia(uploader, update, info, pendingUpdate)
@ -139,6 +146,17 @@ class UpdateStatusTask(
return result
}
private fun regulateStatusText(update: ParcelableStatusUpdate, pending: PendingStatusUpdate) {
val inReplyTo = update.in_reply_to_status ?: return
for (i in 0 until pending.length) {
if (update.accounts[i].type != AccountType.TWITTER) continue
val (replyText, _, excludedMentions) = extractor.extractReplyTextAndMentions(
pending.overrideTexts[i], inReplyTo)
pending.overrideTexts[i] = replyText
pending.excludeReplyUserIds[i] = excludedMentions.map { it.key.id }.toTypedArray()
}
}
private fun deleteOrUpdateDraft(update: ParcelableStatusUpdate, result: UpdateStatusResult,
draftId: Long) {
val where = Expression.equalsArgs(Drafts._ID).sql
@ -226,9 +244,9 @@ class UpdateStatusTask(
val account = update.accounts[i]
val text = pending.overrideTexts[i]
val textLimit = account.textLimit
val ignoreMentions = update.in_reply_to_status != null && account.type ==
AccountType.TWITTER && !defaultFeatures.isMentionsCountsInStatus
if (textLimit >= 0 && validator.getTweetLength(text, ignoreMentions) <= textLimit) {
val ignoreMentions = account.type == AccountType.TWITTER
if (textLimit >= 0 && validator.getTweetLength(text, ignoreMentions,
update.in_reply_to_status) <= textLimit) {
continue
}
shortener.waitForService()
@ -300,13 +318,11 @@ class UpdateStatusTask(
fanfouUpdateStatusWithPhoto(microBlog, statusUpdate, pendingUpdate,
pendingUpdate.overrideTexts[i], account.mediaSizeLimit, i)
} else {
twitterUpdateStatus(microBlog, statusUpdate, pendingUpdate,
pendingUpdate.overrideTexts[i], i)
twitterUpdateStatus(microBlog, statusUpdate, pendingUpdate, i)
}
}
else -> {
twitterUpdateStatus(microBlog, statusUpdate, pendingUpdate,
pendingUpdate.overrideTexts[i], i)
twitterUpdateStatus(microBlog, statusUpdate, pendingUpdate, i)
}
}
result.statuses[i] = ParcelableStatusUtils.fromStatus(requestResult,
@ -392,11 +408,16 @@ class UpdateStatusTask(
@Throws(MicroBlogException::class)
private fun twitterUpdateStatus(microBlog: MicroBlog, statusUpdate: ParcelableStatusUpdate,
pendingUpdate: PendingStatusUpdate, overrideText: String,
index: Int): Status {
pendingUpdate: PendingStatusUpdate, index: Int): Status {
val overrideText = pendingUpdate.overrideTexts[index]
val status = StatusUpdate(overrideText)
if (statusUpdate.in_reply_to_status != null) {
status.inReplyToStatusId(statusUpdate.in_reply_to_status.id)
val inReplyToStatus = statusUpdate.in_reply_to_status
if (inReplyToStatus != null) {
status.inReplyToStatusId(inReplyToStatus.id)
if (statusUpdate.accounts[index].type == AccountType.TWITTER) {
status.autoPopulateReplyMetadata(true)
}
}
if (statusUpdate.repost_status_id != null) {
status.setRepostStatusId(statusUpdate.repost_status_id)
@ -408,9 +429,6 @@ class UpdateStatusTask(
status.location(ParcelableLocationUtils.toGeoLocation(statusUpdate.location))
status.displayCoordinates(statusUpdate.display_coordinates)
}
if (statusUpdate.accounts[index].type == AccountType.TWITTER) {
status.autoPopulateReplyMetadata(true)
}
val mediaIds = pendingUpdate.mediaIds[index]
if (mediaIds != null) {
status.mediaIds(*mediaIds)
@ -469,7 +487,8 @@ class UpdateStatusTask(
private fun getMediaUploader(app: TwidereApplication): MediaUploaderInterface? {
val uploaderComponent = preferences.getString(KEY_MEDIA_UPLOADER, null)
if (ServicePickerPreference.isNoneValue(uploaderComponent)) return null
val uploader = MediaUploaderInterface.getInstance(app, uploaderComponent) ?: throw UploaderNotFoundException(context.getString(R.string.error_message_media_uploader_not_found))
val uploader = MediaUploaderInterface.getInstance(app, uploaderComponent) ?:
throw UploaderNotFoundException(context.getString(R.string.error_message_media_uploader_not_found))
try {
uploader.checkService { metaData ->
if (metaData == null) throw ExtensionVersionMismatchException()
@ -520,6 +539,7 @@ class UpdateStatusTask(
var sharedMediaOwners: Array<UserKey>? = null
val overrideTexts: Array<String> = Array(length) { defaultText }
val excludeReplyUserIds: Array<Array<String>?> = arrayOfNulls(length)
val mediaIds: Array<Array<String>?> = arrayOfNulls(length)
val mediaUploadResults: Array<MediaUploadResult?> = arrayOfNulls(length)

View File

@ -21,18 +21,16 @@ package org.mariotaku.twidere.util
import com.rengwuxian.materialedittext.validation.METLengthChecker
import com.twitter.Validator
import org.mariotaku.twidere.extension.getTweetLength
/**
* Created by mariotaku on 15/4/29.
*/
class TwitterValidatorMETLengthChecker(
private val validator: Validator,
private val ignoreMentions: Boolean
private val validator: Validator
) : METLengthChecker() {
override fun getLength(charSequence: CharSequence): Int {
return validator.getTweetLength(charSequence.toString(), ignoreMentions)
return validator.getTweetLength(charSequence.toString())
}
}