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:
parent
b495048f89
commit
ec6ba7b4e0
@ -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");
|
||||
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package org.mariotaku.twidere.model
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2016/12/7.
|
||||
*/
|
@ -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)
|
||||
|
@ -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"
|
||||
}
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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() {
|
||||
|
@ -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()
|
@ -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>
|
||||
)
|
@ -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()
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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())
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user