diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableStatus.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableStatus.java index 62a137900..3c35fd995 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableStatus.java +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableStatus.java @@ -34,10 +34,10 @@ import org.mariotaku.commons.objectcursor.LoganSquareCursorFieldConverter; import org.mariotaku.library.objectcursor.annotation.AfterCursorObjectCreated; import org.mariotaku.library.objectcursor.annotation.CursorField; import org.mariotaku.library.objectcursor.annotation.CursorObject; -import org.mariotaku.twidere.model.util.LineSeparatedStringArrayConverter; +import org.mariotaku.twidere.model.util.FilterStringsFieldConverter; +import org.mariotaku.twidere.model.util.FilterUserKeysFieldConverter; import org.mariotaku.twidere.model.util.UserKeyConverter; import org.mariotaku.twidere.model.util.UserKeyCursorFieldConverter; -import org.mariotaku.twidere.model.util.UserKeysCursorFieldConverter; import org.mariotaku.twidere.provider.TwidereDataStore; import org.mariotaku.twidere.provider.TwidereDataStore.Statuses; @@ -330,16 +330,16 @@ public class ParcelableStatus implements Parcelable, Comparable { +public class FilterStringsFieldConverter extends AbsArrayCursorFieldConverter { @Override protected String[] newArray(int size) { return new String[size]; @@ -32,12 +28,16 @@ public class LineSeparatedStringArrayConverter extends AbsArrayCursorFieldConver @Override protected String convertToString(String item) { - return item; + if (item == null || item.isEmpty()) return ""; + return '\\' + item + '\\'; } @Override protected String parseItem(String str) { - return str; + if (str == null || str.isEmpty()) return null; + final int len = str.length(); + if (str.charAt(0) != '\\' || str.charAt(len - 1) != '\\') return str; + return str.substring(1, len - 1); } @Override diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/util/FilterUserKeysFieldConverter.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/util/FilterUserKeysFieldConverter.java new file mode 100644 index 000000000..9d19283bf --- /dev/null +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/util/FilterUserKeysFieldConverter.java @@ -0,0 +1,52 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright 2012-2017 Mariotaku Lee + * + * 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.twidere.model.util; + +import org.mariotaku.commons.objectcursor.AbsArrayCursorFieldConverter; +import org.mariotaku.twidere.model.UserKey; + +/** + * Created by mariotaku on 2017/9/12. + */ + +public class FilterUserKeysFieldConverter extends AbsArrayCursorFieldConverter { + @Override + protected UserKey[] newArray(int size) { + return new UserKey[size]; + } + + @Override + protected String convertToString(UserKey item) { + if (item == null) return ""; + return '\\' + item.toString() + '\\'; + } + + @Override + protected UserKey parseItem(String str) { + if (str == null || str.isEmpty()) return null; + final int len = str.length(); + if (str.charAt(0) != '\\' || str.charAt(len - 1) != '\\') return UserKey.valueOf(str); + return UserKey.valueOf(str.substring(1, len - 1)); + } + + @Override + protected char separatorChar() { + return '\n'; + } +} diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/InternalTwitterContentUtils.java b/twidere/src/main/java/org/mariotaku/twidere/util/InternalTwitterContentUtils.java index 5ec794f57..b5d7071e6 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/InternalTwitterContentUtils.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/InternalTwitterContentUtils.java @@ -1,8 +1,6 @@ package org.mariotaku.twidere.util; import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; @@ -18,10 +16,7 @@ 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.ParcelableStatus; import org.mariotaku.twidere.model.SpanItem; -import org.mariotaku.twidere.model.UserKey; -import org.mariotaku.twidere.util.database.FilterQueryBuilder; import java.nio.charset.Charset; import java.util.zip.CRC32; @@ -38,44 +33,6 @@ public class InternalTwitterContentUtils { private InternalTwitterContentUtils() { } - public static boolean isFiltered(final SQLiteDatabase database, final UserKey userKey, - final String textPlain, final String quotedTextPlain, - final SpanItem[] spans, final SpanItem[] quotedSpans, - final String source, final String quotedSource, - final UserKey retweetedByKey, final UserKey quotedUserKey) { - return isFiltered(database, userKey, textPlain, quotedTextPlain, spans, quotedSpans, source, - quotedSource, retweetedByKey, quotedUserKey, true); - } - - - public static boolean isFiltered(final SQLiteDatabase database, final UserKey userKey, - final String textPlain, final String quotedTextPlain, - final SpanItem[] spans, final SpanItem[] quotedSpans, - final String source, final String quotedSource, - final UserKey retweetedByKey, final UserKey quotedUserKey, - final boolean filterRts) { - if (textPlain == null && spans == null && userKey == null && source == null) - return false; - - final Pair query = FilterQueryBuilder.INSTANCE.isFilteredQuery(userKey, - textPlain, quotedTextPlain, spans, quotedSpans, source, quotedSource, retweetedByKey, - quotedUserKey, filterRts); - final Cursor cur = database.rawQuery(query.getFirst(), query.getSecond()); - if (cur == null) return false; - try { - return cur.moveToFirst() && cur.getInt(0) != 0; - } finally { - cur.close(); - } - } - - public static boolean isFiltered(@NonNull final SQLiteDatabase database, - @NonNull final ParcelableStatus status, final boolean filterRTs) { - return isFiltered(database, status.user_key, status.text_plain, status.quoted_text_plain, - status.spans, status.quoted_spans, status.source, status.quoted_source, - status.retweeted_by_user_key, status.quoted_user_key, filterRTs); - } - @NonNull public static String getBestBannerUrl(@NonNull final String baseUrl, final int width, final int height) { final String type; diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/ParcelableStatusExtensions.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/ParcelableStatusExtensions.kt index fd213db20..199b8c98c 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/ParcelableStatusExtensions.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/ParcelableStatusExtensions.kt @@ -83,85 +83,6 @@ inline val ParcelableStatus.can_retweet: Boolean } } -fun ParcelableStatus.toSummaryLine(): ParcelableActivity.SummaryLine { - val result = ParcelableActivity.SummaryLine() - result.key = user_key - result.name = user_name - result.screen_name = user_screen_name - result.content = text_unescaped - return result -} - -fun ParcelableStatus.extractFanfouHashtags(): List { - return spans?.filter { span -> - var link = span.link - if (link.startsWith("/")) { - link = "http://fanfou.com$link" - } - if (UriUtils.getAuthority(link) != "fanfou.com") { - return@filter false - } - if (span.start <= 0 || span.end > text_unescaped.lastIndex) return@filter false - if (text_unescaped[span.start - 1] == '#' && text_unescaped[span.end] == '#') { - return@filter true - } - return@filter false - }?.map { text_unescaped.substring(it.start, it.end) }.orEmpty() -} - - -fun ParcelableStatus.makeOriginal() { - if (!is_retweet) return - id = retweet_id - is_retweet = false - retweeted_by_user_key = null - retweeted_by_user_name = null - retweeted_by_user_screen_name = null - retweeted_by_user_profile_image = null - retweet_timestamp = -1 - retweet_id = null -} - -fun ParcelableStatus.addFilterFlag(@ParcelableStatus.FilterFlags flags: Long) { - filter_flags = filter_flags or flags -} - -fun ParcelableStatus.updateFilterInfo(descriptions: Collection?) { - val links = mutableSetOf() - spans?.mapNotNullTo(links) { span -> - if (span.type != SpanItem.SpanType.LINK) return@mapNotNullTo null - return@mapNotNullTo span.link - } - quoted_spans?.mapNotNullTo(links) { span -> - if (span.type != SpanItem.SpanType.LINK) return@mapNotNullTo null - return@mapNotNullTo span.link - } - filter_links = links.toTypedArray() - - filter_sources = setOf(source?.plainText, quoted_source?.plainText).filterNotNull().toTypedArray() - - filter_users = setOf(user_key, quoted_user_key, retweeted_by_user_key).filterNotNull().toTypedArray() - - filter_names = setOf(user_name, quoted_user_name, retweeted_by_user_name).filterNotNull().toTypedArray() - - val texts = StringBuilder() - texts.appendNonEmptyLine(text_unescaped) - texts.appendNonEmptyLine(quoted_text_unescaped) - media?.forEach { item -> - texts.appendNonEmptyLine(item.alt_text) - } - quoted_media?.forEach { item -> - texts.appendNonEmptyLine(item.alt_text) - } - filter_texts = texts.toString() - - filter_descriptions = descriptions?.filterNotNull()?.joinToString("\n") -} - -fun ParcelableStatus.updateExtraInformation(details: AccountDetails) { - account_color = details.color -} - val ParcelableStatus.quoted: ParcelableStatus? get() { val obj = ParcelableStatus() @@ -182,6 +103,95 @@ val ParcelableStatus.quoted: ParcelableStatus? return obj } +fun ParcelableStatus.toSummaryLine(): ParcelableActivity.SummaryLine { + val result = ParcelableActivity.SummaryLine() + result.key = user_key + result.name = user_name + result.screen_name = user_screen_name + result.content = text_unescaped + return result +} + + +fun ParcelableStatus.extractFanfouHashtags(): List { + return spans?.filter { span -> + var link = span.link + if (link.startsWith("/")) { + link = "http://fanfou.com$link" + } + if (UriUtils.getAuthority(link) != "fanfou.com") { + return@filter false + } + if (span.start <= 0 || span.end > text_unescaped.lastIndex) return@filter false + if (text_unescaped[span.start - 1] == '#' && text_unescaped[span.end] == '#') { + return@filter true + } + return@filter false + }?.map { text_unescaped.substring(it.start, it.end) }.orEmpty() +} + +fun ParcelableStatus.makeOriginal() { + if (!is_retweet) return + id = retweet_id + is_retweet = false + retweeted_by_user_key = null + retweeted_by_user_name = null + retweeted_by_user_screen_name = null + retweeted_by_user_profile_image = null + retweet_timestamp = -1 + retweet_id = null +} + +fun ParcelableStatus.addFilterFlag(@ParcelableStatus.FilterFlags flags: Long) { + filter_flags = filter_flags or flags +} + +fun ParcelableStatus.updateFilterInfo(descriptions: Collection?) { + updateContentFilterInfo() + filter_users = setOf(user_key, quoted_user_key, retweeted_by_user_key).filterNotNull().toTypedArray() + filter_names = setOf(user_name, quoted_user_name, retweeted_by_user_name).filterNotNull().toTypedArray() + filter_descriptions = descriptions?.filterNotNull()?.joinToString("\n") +} + +fun ParcelableStatus.updateContentFilterInfo() { + filter_links = generateFilterLinks() + filter_texts = generateFilterTexts() + + filter_sources = setOf(source?.plainText, quoted_source?.plainText).filterNotNull().toTypedArray() +} + +fun ParcelableStatus.generateFilterTexts(): String { + val texts = StringBuilder() + texts.appendNonEmptyLine(text_unescaped) + texts.appendNonEmptyLine(quoted_text_unescaped) + media?.forEach { item -> + texts.appendNonEmptyLine(item.alt_text) + } + quoted_media?.forEach { item -> + texts.appendNonEmptyLine(item.alt_text) + } + return texts.toString() +} + +fun ParcelableStatus.generateFilterLinks(): Array { + val links = mutableSetOf() + spans?.mapNotNullTo(links) { span -> + if (span.type != SpanItem.SpanType.LINK) return@mapNotNullTo null + return@mapNotNullTo span.link + } + quoted_spans?.mapNotNullTo(links) { span -> + if (span.type != SpanItem.SpanType.LINK) return@mapNotNullTo null + return@mapNotNullTo span.link + } + return links.toTypedArray() +} + +fun ParcelableStatus.updateExtraInformation(details: AccountDetails) { + account_color = details.color +} + +internal inline val String.plainText: String get() = HtmlEscapeHelper.toPlainText(this) + private fun parcelableUserMention(key: UserKey, name: String, screenName: String) = ParcelableUserMention().also { it.key = key it.name = name @@ -193,5 +203,3 @@ private fun StringBuilder.appendNonEmptyLine(line: CharSequence?) { append(line) append('\n') } - -private val String.plainText: String get() = HtmlEscapeHelper.toPlainText(this) \ No newline at end of file diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/api/StatusExtensions.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/api/StatusExtensions.kt index fa2431720..43653b246 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/api/StatusExtensions.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/api/StatusExtensions.kt @@ -33,6 +33,7 @@ import org.mariotaku.microblog.library.twitter.model.Status import org.mariotaku.twidere.exception.MalformedResponseException import org.mariotaku.twidere.extension.model.addFilterFlag import org.mariotaku.twidere.extension.model.toParcelable +import org.mariotaku.twidere.extension.model.updateContentFilterInfo import org.mariotaku.twidere.extension.model.updateFilterInfo import org.mariotaku.twidere.extension.toSpanItem import org.mariotaku.twidere.model.* @@ -46,19 +47,23 @@ import org.mariotaku.twidere.util.InternalTwitterContentUtils import org.mariotaku.twidere.util.InternalTwitterContentUtils.getMediaUrl import org.mariotaku.twidere.util.InternalTwitterContentUtils.getStartEndForEntity -fun Status.toParcelable(details: AccountDetails, profileImageSize: String = "normal"): ParcelableStatus { - return toParcelable(details.key, details.type, profileImageSize).apply { +fun Status.toParcelable(details: AccountDetails, profileImageSize: String = "normal", + updateFilterInfoAction: (Status, ParcelableStatus) -> Unit = ::updateFilterInfoDefault): ParcelableStatus { + return toParcelable(details.key, details.type, profileImageSize, updateFilterInfoAction).apply { account_color = details.color } } -fun Status.toParcelable(accountKey: UserKey, accountType: String, profileImageSize: String = "normal"): ParcelableStatus { +fun Status.toParcelable(accountKey: UserKey, accountType: String, profileImageSize: String = "normal", + updateFilterInfoAction: (Status, ParcelableStatus) -> Unit = ::updateFilterInfoDefault): ParcelableStatus { val result = ParcelableStatus() - applyTo(accountKey, accountType, profileImageSize, result) + applyTo(accountKey, accountType, profileImageSize, result, updateFilterInfoAction) return result } -fun Status.applyTo(accountKey: UserKey, accountType: String, profileImageSize: String = "normal", result: ParcelableStatus) { +fun Status.applyTo(accountKey: UserKey, accountType: String, profileImageSize: String = "normal", + result: ParcelableStatus, + updateFilterInfoAction: (Status, ParcelableStatus) -> Unit = ::updateFilterInfoDefault) { val extras = ParcelableStatus.Extras() result.account_key = accountKey result.id = id @@ -201,10 +206,7 @@ fun Status.applyTo(accountKey: UserKey, accountType: String, profileImageSize: S result.addFilterFlag(ParcelableStatus.FilterFlags.HAS_MEDIA) } - result.updateFilterInfo(setOf(userDescriptionUnescaped, retweetedStatus?.userDescriptionUnescaped, - quotedStatus?.userDescriptionUnescaped, this.user.location, retweetedStatus?.user?.location, - quotedStatus?.user?.location, userUrlExpanded, retweetedStatus?.userUrlExpanded, - quotedStatus?.userUrlExpanded)) + updateFilterInfoAction(this, result) } @@ -228,7 +230,6 @@ fun Status.formattedTextWithIndices(): StatusTextWithIndices { return textWithIndices } - fun CodePointArray.findResultRangeLength(spans: Array, origStart: Int, origEnd: Int): Int { val findResult = findByOrigRange(spans, origStart, origEnd) if (findResult.isEmpty()) { @@ -241,7 +242,6 @@ fun CodePointArray.findResultRangeLength(spans: Array, origStart: Int, return charCount(origStart, first.orig_start) + (last.end - first.start) + charCount(first.orig_end, origEnd) } - fun HtmlBuilder.addEntities(entities: EntitySupport) { // Format media. var mediaEntities: Array? = null @@ -268,16 +268,62 @@ fun HtmlBuilder.addEntities(entities: EntitySupport) { } } + +fun updateFilterInfoDefault(status: Status, result: ParcelableStatus) { + result.updateFilterInfo(setOf( + status.userDescriptionUnescaped, + status.userUrlExpanded, + status.userLocation, + status.retweetedStatus?.userDescriptionUnescaped, + status.retweetedStatus?.userLocation, + status.retweetedStatus?.userUrlExpanded, + status.quotedStatus?.userDescriptionUnescaped, + status.quotedStatus?.userLocation, + status.quotedStatus?.userUrlExpanded + )) +} + +/** + * Ignores status user info + */ +fun updateFilterInfoForUserTimeline(status: Status, result: ParcelableStatus) { + result.updateContentFilterInfo() + + if (result.is_retweet) { + result.filter_users = setOf(result.user_key, result.quoted_user_key).filterNotNull().toTypedArray() + result.filter_names = setOf(result.user_name, result.quoted_user_name).filterNotNull().toTypedArray() + result.filter_descriptions = setOf( + status.retweetedStatus?.userDescriptionUnescaped, + status.retweetedStatus?.userUrlExpanded, + status.retweetedStatus?.userLocation, + status.quotedStatus?.userDescriptionUnescaped, + status.quotedStatus?.userLocation, + status.quotedStatus?.userUrlExpanded + ).filterNotNull().joinToString("\n") + } else { + result.filter_users = setOf(result.quoted_user_key).filterNotNull().toTypedArray() + result.filter_names = setOf(result.quoted_user_name).filterNotNull().toTypedArray() + result.filter_descriptions = setOf( + status.quotedStatus?.userDescriptionUnescaped, + status.quotedStatus?.userLocation, + status.quotedStatus?.userUrlExpanded + ).filterNotNull().joinToString("\n") + } +} + private fun String.twitterUnescaped(): String { return twitterRawTextTranslator.translate(this) } -private val Status.userDescriptionUnescaped: String? +private inline val Status.userDescriptionUnescaped: String? get() = user?.let { InternalTwitterContentUtils.formatUserDescription(it)?.first } -private val Status.userUrlExpanded: String? +private inline val Status.userUrlExpanded: String? get() = user?.urlEntities?.firstOrNull()?.expandedUrl +private inline val Status.userLocation: String? + get() = user?.location + /** * @param spans Ordered spans * * diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/AbsRequestStatusesLoader.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/AbsRequestStatusesLoader.kt index 9e52a0e4e..d30ec1f47 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/AbsRequestStatusesLoader.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/AbsRequestStatusesLoader.kt @@ -22,7 +22,6 @@ package org.mariotaku.twidere.loader.statuses import android.accounts.AccountManager import android.content.Context import android.content.SharedPreferences -import android.database.sqlite.SQLiteDatabase import android.support.annotation.WorkerThread import org.mariotaku.kpreferences.get import org.mariotaku.microblog.library.MicroBlogException @@ -30,7 +29,6 @@ import org.mariotaku.microblog.library.twitter.model.Paging import org.mariotaku.microblog.library.twitter.model.Status import org.mariotaku.twidere.R import org.mariotaku.twidere.TwidereConstants.LOGTAG -import org.mariotaku.twidere.app.TwidereApplication import org.mariotaku.twidere.constant.loadItemLimitKey import org.mariotaku.twidere.extension.model.api.applyLoadLimit import org.mariotaku.twidere.loader.iface.IPaginationLoader @@ -170,8 +168,7 @@ abstract class AbsRequestStatusesLoader( data.addAll(statuses) } - val db = TwidereApplication.getInstance(context).sqLiteDatabase - data.forEach { it.is_filtered = shouldFilterStatus(db, it) } + data.forEach { it.is_filtered = shouldFilterStatus(it) } if (comparator != null) { data.sortWith(comparator!!) @@ -188,8 +185,7 @@ abstract class AbsRequestStatusesLoader( } @WorkerThread - protected abstract fun shouldFilterStatus(database: SQLiteDatabase, status: ParcelableStatus): Boolean - + protected abstract fun shouldFilterStatus(status: ParcelableStatus): Boolean protected open fun processPaging(paging: Paging, details: AccountDetails, loadItemLimit: Int) { paging.applyLoadLimit(details, loadItemLimit) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/ConversationLoader.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/ConversationLoader.kt index a2e821985..1cf838c57 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/ConversationLoader.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/ConversationLoader.kt @@ -20,7 +20,6 @@ package org.mariotaku.twidere.loader.statuses import android.content.Context -import android.database.sqlite.SQLiteDatabase import android.support.annotation.WorkerThread import org.attoparser.config.ParseConfiguration import org.attoparser.dom.DOMMarkupParser @@ -48,7 +47,7 @@ import org.mariotaku.twidere.model.pagination.PaginatedArrayList import org.mariotaku.twidere.model.pagination.PaginatedList import org.mariotaku.twidere.model.pagination.Pagination import org.mariotaku.twidere.model.pagination.SinceMaxPagination -import org.mariotaku.twidere.util.InternalTwitterContentUtils +import org.mariotaku.twidere.util.database.ContentFiltersUtils import java.text.ParseException import java.util.* @@ -120,8 +119,8 @@ class ConversationLoader( } @WorkerThread - override fun shouldFilterStatus(database: SQLiteDatabase, status: ParcelableStatus): Boolean { - return InternalTwitterContentUtils.isFiltered(database, status, false) + override fun shouldFilterStatus(status: ParcelableStatus): Boolean { + return ContentFiltersUtils.isFiltered(context.contentResolver, status, false, 0) } @Throws(MicroBlogException::class) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/DummyParcelableStatusesLoader.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/DummyParcelableStatusesLoader.kt deleted file mode 100644 index 3a33bf203..000000000 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/DummyParcelableStatusesLoader.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Twidere - Twitter client for Android - * - * Copyright (C) 2012-2017 Mariotaku Lee - * - * 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 . - */ - -package org.mariotaku.twidere.loader.statuses - -import android.content.Context -import org.mariotaku.twidere.model.ListResponse -import org.mariotaku.twidere.model.ParcelableStatus - -class DummyParcelableStatusesLoader @JvmOverloads constructor(context: Context, data: List? = null, fromUser: Boolean = false) : ParcelableStatusesLoader(context, data, -1, fromUser) { - - override fun loadInBackground(): ListResponse { - return ListResponse.getListInstance(data) - } - -} diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/GroupTimelineLoader.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/GroupTimelineLoader.kt index 6e46faea0..e8f42642a 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/GroupTimelineLoader.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/GroupTimelineLoader.kt @@ -20,13 +20,13 @@ package org.mariotaku.twidere.loader.statuses import android.content.Context -import android.database.sqlite.SQLiteDatabase import android.support.annotation.WorkerThread import org.mariotaku.microblog.library.MicroBlog import org.mariotaku.microblog.library.MicroBlogException import org.mariotaku.microblog.library.twitter.model.Paging import org.mariotaku.microblog.library.twitter.model.Status import org.mariotaku.twidere.annotation.AccountType +import org.mariotaku.twidere.annotation.FilterScope import org.mariotaku.twidere.exception.APINotSupportedException import org.mariotaku.twidere.extension.model.api.toParcelable import org.mariotaku.twidere.extension.model.newMicroBlogInstance @@ -34,7 +34,7 @@ import org.mariotaku.twidere.model.AccountDetails import org.mariotaku.twidere.model.ParcelableStatus import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.pagination.PaginatedList -import org.mariotaku.twidere.util.InternalTwitterContentUtils +import org.mariotaku.twidere.util.database.ContentFiltersUtils class GroupTimelineLoader( context: Context, @@ -57,18 +57,19 @@ class GroupTimelineLoader( } } @WorkerThread - override fun shouldFilterStatus(database: SQLiteDatabase, status: ParcelableStatus): Boolean { - return InternalTwitterContentUtils.isFiltered(database, status, true) + override fun shouldFilterStatus(status: ParcelableStatus): Boolean { + return ContentFiltersUtils.isFiltered(context.contentResolver, status, true, + FilterScope.LIST_GROUP_TIMELINE) } private fun getMicroBlogStatuses(account: AccountDetails, paging: Paging): List { val microBlog = account.newMicroBlogInstance(context, MicroBlog::class.java) - when { + return when { groupId != null -> { - return microBlog.getGroupStatuses(groupId, paging) + microBlog.getGroupStatuses(groupId, paging) } groupName != null -> { - return microBlog.getGroupStatusesByName(groupName, paging) + microBlog.getGroupStatusesByName(groupName, paging) } else -> { throw MicroBlogException("No group name or id given") diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/IntentExtrasStatusesLoader.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/IntentExtrasStatusesLoader.kt deleted file mode 100644 index 2a5ce6a28..000000000 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/IntentExtrasStatusesLoader.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Twidere - Twitter client for Android - * - * Copyright (C) 2012-2017 Mariotaku Lee - * - * 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 . - */ - -package org.mariotaku.twidere.loader.statuses - -import android.content.Context -import android.os.Bundle -import org.mariotaku.twidere.constant.IntentConstants.EXTRA_STATUSES -import org.mariotaku.twidere.model.ListResponse -import org.mariotaku.twidere.model.ParcelableStatus -import java.util.* - -class IntentExtrasStatusesLoader(context: Context, private val mExtras: Bundle?, - data: List, fromUser: Boolean) : ParcelableStatusesLoader(context, data, -1, fromUser) { - - override fun loadInBackground(): ListResponse { - if (mExtras != null && mExtras.containsKey(EXTRA_STATUSES)) { - val users = mExtras.getParcelableArrayList(EXTRA_STATUSES) - if (users != null) { - data.addAll(users) - Collections.sort(data) - } - } - return ListResponse.getListInstance(data) - } - -} diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/MediaStatusesSearchLoader.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/MediaStatusesSearchLoader.kt index d16a8be66..5578e89c8 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/MediaStatusesSearchLoader.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/MediaStatusesSearchLoader.kt @@ -20,7 +20,6 @@ package org.mariotaku.twidere.loader.statuses import android.content.Context -import android.database.sqlite.SQLiteDatabase import android.support.annotation.WorkerThread import org.mariotaku.ktextension.isNullOrEmpty import org.mariotaku.microblog.library.MicroBlog @@ -30,6 +29,7 @@ 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 @@ -37,7 +37,7 @@ import org.mariotaku.twidere.model.AccountDetails import org.mariotaku.twidere.model.ParcelableStatus import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.pagination.PaginatedList -import org.mariotaku.twidere.util.InternalTwitterContentUtils +import org.mariotaku.twidere.util.database.ContentFiltersUtils open class MediaStatusesSearchLoader( context: Context, @@ -60,9 +60,11 @@ open class MediaStatusesSearchLoader( } @WorkerThread - override fun shouldFilterStatus(database: SQLiteDatabase, status: ParcelableStatus): Boolean { + override fun shouldFilterStatus(status: ParcelableStatus): Boolean { if (status.media.isNullOrEmpty()) return true - return InternalTwitterContentUtils.isFiltered(database, status, true) + val allowed = query?.split(' ')?.toTypedArray() + return ContentFiltersUtils.isFiltered(context.contentResolver, status, true, + FilterScope.SEARCH_RESULTS, allowed) } override fun processPaging(paging: Paging, details: AccountDetails, loadItemLimit: Int) { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/MediaTimelineLoader.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/MediaTimelineLoader.kt index e5aea75bd..6ce6ad361 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/MediaTimelineLoader.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/MediaTimelineLoader.kt @@ -20,7 +20,6 @@ package org.mariotaku.twidere.loader.statuses import android.content.Context -import android.database.sqlite.SQLiteDatabase import android.support.annotation.WorkerThread import org.mariotaku.ktextension.isNullOrEmpty import org.mariotaku.microblog.library.MicroBlog @@ -29,10 +28,12 @@ import org.mariotaku.microblog.library.mastodon.Mastodon import org.mariotaku.microblog.library.twitter.model.* import org.mariotaku.twidere.alias.MastodonTimelineOption import org.mariotaku.twidere.annotation.AccountType +import org.mariotaku.twidere.annotation.FilterScope import org.mariotaku.twidere.extension.api.tryShowUser 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 @@ -40,7 +41,7 @@ import org.mariotaku.twidere.model.ParcelableStatus import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.pagination.PaginatedList import org.mariotaku.twidere.util.DataStoreUtils -import org.mariotaku.twidere.util.InternalTwitterContentUtils +import org.mariotaku.twidere.util.database.ContentFiltersUtils class MediaTimelineLoader( context: Context, @@ -69,21 +70,20 @@ class MediaTimelineLoader( @Throws(MicroBlogException::class) override fun getStatuses(account: AccountDetails, paging: Paging): PaginatedList { - when (account.type) { - AccountType.MASTODON -> return getMastodonStatuses(account, paging) - else -> return getMicroBlogStatuses(account, paging).mapMicroBlogToPaginated { - it.toParcelable(account, profileImageSize) + return when (account.type) { + AccountType.MASTODON -> getMastodonStatuses(account, paging) + else -> getMicroBlogStatuses(account, paging).mapMicroBlogToPaginated { + it.toParcelable(account, profileImageSize = profileImageSize, + updateFilterInfoAction = ::updateFilterInfoForUserTimeline) } } } @WorkerThread - override fun shouldFilterStatus(database: SQLiteDatabase, status: ParcelableStatus): Boolean { + override fun shouldFilterStatus(status: ParcelableStatus): Boolean { if (status.media.isNullOrEmpty()) return false - val retweetUserKey = status.user_key.takeIf { status.is_retweet } - return !isMyTimeline && InternalTwitterContentUtils.isFiltered(database, retweetUserKey, - status.text_plain, status.quoted_text_plain, status.spans, status.quoted_spans, - status.source, status.quoted_source, null, status.quoted_user_key) + return !isMyTimeline && ContentFiltersUtils.isFiltered(context.contentResolver, status, + true, FilterScope.USER_TIMELINE) } private fun getMicroBlogStatuses(account: AccountDetails, paging: Paging): ResponseList { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/NetworkPublicTimelineLoader.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/NetworkPublicTimelineLoader.kt index c5512d172..55185d72f 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/NetworkPublicTimelineLoader.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/NetworkPublicTimelineLoader.kt @@ -20,13 +20,13 @@ package org.mariotaku.twidere.loader.statuses import android.content.Context -import android.database.sqlite.SQLiteDatabase import android.support.annotation.WorkerThread import org.mariotaku.microblog.library.MicroBlog import org.mariotaku.microblog.library.MicroBlogException import org.mariotaku.microblog.library.mastodon.Mastodon import org.mariotaku.microblog.library.twitter.model.Paging import org.mariotaku.twidere.annotation.AccountType +import org.mariotaku.twidere.annotation.FilterScope import org.mariotaku.twidere.exception.APINotSupportedException import org.mariotaku.twidere.extension.model.api.mastodon.mapToPaginated import org.mariotaku.twidere.extension.model.api.mastodon.toParcelable @@ -36,7 +36,7 @@ import org.mariotaku.twidere.model.AccountDetails import org.mariotaku.twidere.model.ParcelableStatus import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.pagination.PaginatedList -import org.mariotaku.twidere.util.InternalTwitterContentUtils +import org.mariotaku.twidere.util.database.ContentFiltersUtils class NetworkPublicTimelineLoader( context: Context, @@ -68,7 +68,8 @@ class NetworkPublicTimelineLoader( } @WorkerThread - override fun shouldFilterStatus(database: SQLiteDatabase, status: ParcelableStatus): Boolean { - return InternalTwitterContentUtils.isFiltered(database, status, true) + override fun shouldFilterStatus(status: ParcelableStatus): Boolean { + return ContentFiltersUtils.isFiltered(context.contentResolver, status, true, + FilterScope.SEARCH_RESULTS) } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/PublicTimelineLoader.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/PublicTimelineLoader.kt index b3caea3c2..ecfc8d645 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/PublicTimelineLoader.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/PublicTimelineLoader.kt @@ -20,13 +20,13 @@ package org.mariotaku.twidere.loader.statuses import android.content.Context -import android.database.sqlite.SQLiteDatabase import android.support.annotation.WorkerThread import org.mariotaku.microblog.library.MicroBlog import org.mariotaku.microblog.library.MicroBlogException import org.mariotaku.microblog.library.mastodon.Mastodon import org.mariotaku.microblog.library.twitter.model.Paging import org.mariotaku.twidere.annotation.AccountType +import org.mariotaku.twidere.annotation.FilterScope 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 @@ -35,7 +35,7 @@ import org.mariotaku.twidere.model.AccountDetails import org.mariotaku.twidere.model.ParcelableStatus import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.pagination.PaginatedList -import org.mariotaku.twidere.util.InternalTwitterContentUtils +import org.mariotaku.twidere.util.database.ContentFiltersUtils class PublicTimelineLoader( context: Context, @@ -66,7 +66,8 @@ class PublicTimelineLoader( } @WorkerThread - override fun shouldFilterStatus(database: SQLiteDatabase, status: ParcelableStatus): Boolean { - return InternalTwitterContentUtils.isFiltered(database, status, true) + override fun shouldFilterStatus(status: ParcelableStatus): Boolean { + return ContentFiltersUtils.isFiltered(context.contentResolver, status, true, + FilterScope.SEARCH_RESULTS) } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/TweetSearchLoader.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/TweetSearchLoader.kt index 491531099..88cb779cb 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/TweetSearchLoader.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/TweetSearchLoader.kt @@ -20,7 +20,6 @@ package org.mariotaku.twidere.loader.statuses import android.content.Context -import android.database.sqlite.SQLiteDatabase import android.support.annotation.WorkerThread import org.mariotaku.microblog.library.MicroBlog import org.mariotaku.microblog.library.MicroBlogException @@ -30,6 +29,7 @@ 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.mastodon.mapToPaginated import org.mariotaku.twidere.extension.model.api.mastodon.toParcelable import org.mariotaku.twidere.extension.model.api.toParcelable @@ -41,7 +41,7 @@ import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.pagination.PaginatedList import org.mariotaku.twidere.model.pagination.Pagination import org.mariotaku.twidere.model.pagination.SinceMaxPagination -import org.mariotaku.twidere.util.InternalTwitterContentUtils +import org.mariotaku.twidere.util.database.ContentFiltersUtils open class TweetSearchLoader( context: Context, @@ -59,22 +59,24 @@ open class TweetSearchLoader( @Throws(MicroBlogException::class) override fun getStatuses(account: AccountDetails, paging: Paging): PaginatedList { - when (account.type) { - AccountType.MASTODON -> return getMastodonStatuses(account, paging) - else -> return getMicroBlogStatuses(account, paging).mapMicroBlogToPaginated { + return when (account.type) { + AccountType.MASTODON -> getMastodonStatuses(account, paging) + else -> getMicroBlogStatuses(account, paging).mapMicroBlogToPaginated { it.toParcelable(account, profileImageSize) } } } @WorkerThread - override fun shouldFilterStatus(database: SQLiteDatabase, status: ParcelableStatus): Boolean { - return InternalTwitterContentUtils.isFiltered(database, status, true) + override fun shouldFilterStatus(status: ParcelableStatus): Boolean { + val allowed = query?.split(' ')?.toTypedArray() + return ContentFiltersUtils.isFiltered(context.contentResolver, status, true, + FilterScope.SEARCH_RESULTS, allowed) } protected open fun processQuery(details: AccountDetails, query: String): String { if (details.type == AccountType.TWITTER) { - if (details.extras?.official ?: false) { + if (details.extras?.official == true) { return smQuery(query, pagination) } return "$query exclude:retweets" diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/UserFavoritesLoader.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/UserFavoritesLoader.kt index 69222075e..583a6f990 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/UserFavoritesLoader.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/UserFavoritesLoader.kt @@ -20,7 +20,6 @@ package org.mariotaku.twidere.loader.statuses import android.content.Context -import android.database.sqlite.SQLiteDatabase import android.support.annotation.WorkerThread import org.mariotaku.microblog.library.MicroBlog import org.mariotaku.microblog.library.MicroBlogException @@ -29,6 +28,7 @@ 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.twidere.annotation.AccountType +import org.mariotaku.twidere.annotation.FilterScope 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 @@ -37,7 +37,7 @@ import org.mariotaku.twidere.model.AccountDetails import org.mariotaku.twidere.model.ParcelableStatus import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.pagination.PaginatedList -import org.mariotaku.twidere.util.InternalTwitterContentUtils +import org.mariotaku.twidere.util.database.ContentFiltersUtils class UserFavoritesLoader( context: Context, @@ -64,8 +64,9 @@ class UserFavoritesLoader( } @WorkerThread - override fun shouldFilterStatus(database: SQLiteDatabase, status: ParcelableStatus): Boolean { - return InternalTwitterContentUtils.isFiltered(database, status, false) + override fun shouldFilterStatus(status: ParcelableStatus): Boolean { + return ContentFiltersUtils.isFiltered(context.contentResolver, status, false, + FilterScope.FAVORITES) } private fun getMicroBlogStatuses(account: AccountDetails, paging: Paging): ResponseList { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/UserListTimelineLoader.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/UserListTimelineLoader.kt index a46e55360..a8a301ccd 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/UserListTimelineLoader.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/UserListTimelineLoader.kt @@ -20,20 +20,20 @@ package org.mariotaku.twidere.loader.statuses import android.content.Context -import android.database.sqlite.SQLiteDatabase import android.support.annotation.WorkerThread import org.mariotaku.microblog.library.MicroBlog 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.twidere.annotation.FilterScope import org.mariotaku.twidere.extension.model.api.toParcelable import org.mariotaku.twidere.extension.model.newMicroBlogInstance import org.mariotaku.twidere.model.AccountDetails import org.mariotaku.twidere.model.ParcelableStatus import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.pagination.PaginatedList -import org.mariotaku.twidere.util.InternalTwitterContentUtils +import org.mariotaku.twidere.util.database.ContentFiltersUtils class UserListTimelineLoader( context: Context, @@ -57,8 +57,9 @@ class UserListTimelineLoader( } @WorkerThread - override fun shouldFilterStatus(database: SQLiteDatabase, status: ParcelableStatus): Boolean { - return InternalTwitterContentUtils.isFiltered(database, status, true) + override fun shouldFilterStatus(status: ParcelableStatus): Boolean { + return ContentFiltersUtils.isFiltered(context.contentResolver, status, true, + FilterScope.LIST_GROUP_TIMELINE) } private fun getMicroBlogStatuses(account: AccountDetails, paging: Paging): ResponseList { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/UserMentionsLoader.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/UserMentionsLoader.kt index 50c4a76f0..e8075a7f0 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/UserMentionsLoader.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/UserMentionsLoader.kt @@ -43,7 +43,7 @@ class UserMentionsLoader( override fun processQuery(details: AccountDetails, query: String): String { val screenName = query.substringAfter("@") if (details.type == AccountType.TWITTER) { - if (details.extras?.official ?: false) { + if (details.extras?.official == true) { return smQuery("to:$screenName", pagination) } return "to:$screenName exclude:retweets" diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/UserTimelineLoader.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/UserTimelineLoader.kt index f197b87a9..74629f38f 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/UserTimelineLoader.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/UserTimelineLoader.kt @@ -20,7 +20,6 @@ package org.mariotaku.twidere.loader.statuses import android.content.Context -import android.database.sqlite.SQLiteDatabase import android.support.annotation.WorkerThread import android.text.TextUtils import okhttp3.HttpUrl @@ -42,18 +41,20 @@ import org.mariotaku.restfu.http.mime.SimpleBody import org.mariotaku.twidere.alias.MastodonStatus import org.mariotaku.twidere.alias.MastodonTimelineOption import org.mariotaku.twidere.annotation.AccountType +import org.mariotaku.twidere.annotation.FilterScope import org.mariotaku.twidere.extension.api.tryShowUser 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.newMicroBlogInstance import org.mariotaku.twidere.model.AccountDetails import org.mariotaku.twidere.model.ParcelableStatus import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.timeline.UserTimelineFilter -import org.mariotaku.twidere.util.InternalTwitterContentUtils import org.mariotaku.twidere.util.JsonSerializer import org.mariotaku.twidere.util.dagger.DependencyHolder +import org.mariotaku.twidere.util.database.ContentFiltersUtils import java.io.IOException import java.util.concurrent.atomic.AtomicReference @@ -86,12 +87,13 @@ class UserTimelineLoader( it.toParcelable(account) } else -> getMicroBlogStatuses(account, paging).mapMicroBlogToPaginated { - it.toParcelable(account, profileImageSize = profileImageSize) + it.toParcelable(account, profileImageSize = profileImageSize, + updateFilterInfoAction = ::updateFilterInfoForUserTimeline) } } @WorkerThread - override fun shouldFilterStatus(database: SQLiteDatabase, status: ParcelableStatus): Boolean { + override fun shouldFilterStatus(status: ParcelableStatus): Boolean { if (timelineFilter != null) { if (status.is_retweet && !timelineFilter.isIncludeRetweets) { return true @@ -99,10 +101,8 @@ class UserTimelineLoader( } if (accountKey != null && userKey != null && TextUtils.equals(accountKey.id, userKey.id)) return false - val retweetUserKey = status.user_key.takeIf { status.is_retweet } - return InternalTwitterContentUtils.isFiltered(database, retweetUserKey, status.text_plain, - status.quoted_text_plain, status.spans, status.quoted_spans, status.source, - status.quoted_source, null, status.quoted_user_key) + return ContentFiltersUtils.isFiltered(context.contentResolver, status, true, + FilterScope.USER_TIMELINE) } private fun getMastodonStatuses(account: AccountDetails, paging: Paging): LinkHeaderList { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/DataStoreUtils.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/DataStoreUtils.kt index 4102649d1..70ce50445 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/util/DataStoreUtils.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/DataStoreUtils.kt @@ -493,13 +493,19 @@ object DataStoreUtils { fun ContainsExpression(dataField: String, filterTable: String, filterField: String) = Expression.likeRaw(Column(Table(table), dataField), "'%'||$filterTable.$filterField||'%'") + fun LineContainsExpression(dataField: String, filterTable: String, filterField: String) = + Expression.likeRaw(Column(Table(table), dataField), "'\\%'||$filterTable.$filterField||'%\\'") + + fun LineMatchExpression(dataField: String, filterTable: String, filterField: String) = + Expression.likeRaw(Column(Table(table), dataField), "'%\\'||$filterTable.$filterField||'\\%'") + val filteredUsersWhere = Expression.and( ScopeMatchesExpression(Filters.Users.TABLE_NAME, Filters.Users.SCOPE), - ContainsExpression(Statuses.FILTER_USERS, Filters.Users.TABLE_NAME, Filters.Users.USER_KEY) + LineMatchExpression(Statuses.FILTER_USERS, Filters.Users.TABLE_NAME, Filters.Users.USER_KEY) ) val filteredSourcesWhere = Expression.and( ScopeMatchesExpression(Filters.Sources.TABLE_NAME, Filters.Sources.SCOPE), - ContainsExpression(Statuses.FILTER_SOURCES, Filters.Sources.TABLE_NAME, Filters.Sources.VALUE) + LineMatchExpression(Statuses.FILTER_SOURCES, Filters.Sources.TABLE_NAME, Filters.Sources.VALUE) ) val filteredTextKeywordsWhere = Expression.or( Expression.and( @@ -513,7 +519,7 @@ object DataStoreUtils { Expression.and( Expression.notEquals("${Filters.Keywords.TABLE_NAME}.${Filters.Keywords.SCOPE} & ${FilterScope.TARGET_NAME}", 0), ScopeMatchesExpression(Filters.Keywords.TABLE_NAME, Filters.Keywords.SCOPE), - ContainsExpression(Statuses.FILTER_NAMES, Filters.Keywords.TABLE_NAME, Filters.Keywords.VALUE) + LineMatchExpression(Statuses.FILTER_NAMES, Filters.Keywords.TABLE_NAME, Filters.Keywords.VALUE) ), Expression.and( Expression.notEquals("${Filters.Keywords.TABLE_NAME}.${Filters.Keywords.SCOPE} & ${FilterScope.TARGET_DESCRIPTION}", 0), @@ -523,7 +529,7 @@ object DataStoreUtils { ) val filteredLinksWhere = Expression.and( ScopeMatchesExpression(Filters.Links.TABLE_NAME, Filters.Links.SCOPE), - ContainsExpression(Statuses.FILTER_LINKS, Filters.Links.TABLE_NAME, Filters.Links.VALUE) + LineContainsExpression(Statuses.FILTER_LINKS, Filters.Links.TABLE_NAME, Filters.Links.VALUE) ) val filteredIdsQueryBuilder = SQLQueryBuilder .select(Column(Table(table), Statuses._ID)) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/database/ContentFiltersUtils.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/database/ContentFiltersUtils.kt new file mode 100644 index 000000000..5567e1fd7 --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/database/ContentFiltersUtils.kt @@ -0,0 +1,157 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * 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 . + */ + +package org.mariotaku.twidere.util.database + +import android.content.ContentResolver +import org.mariotaku.twidere.annotation.FilterScope +import org.mariotaku.twidere.extension.rawQuery +import org.mariotaku.twidere.model.ParcelableStatus +import org.mariotaku.twidere.model.UserKey +import org.mariotaku.twidere.provider.TwidereDataStore.Filters.* + +/** + * Created by mariotaku on 2017/2/16. + */ + +object ContentFiltersUtils { + + fun isFiltered(cr: ContentResolver, status: ParcelableStatus, filterRts: Boolean, + @FilterScope scope: Int, allowedKeywords: Array? = null): Boolean { + return isFiltered(cr, status.filter_users, status.filter_texts, + status.filter_sources, status.filter_links, status.filter_names, + status.filter_descriptions, filterRts, scope, allowedKeywords) + } + + fun isFiltered(cr: ContentResolver, users: Array?, texts: String?, sources: Array?, + links: Array?, names: Array?, descriptions: String?, filterRts: Boolean, + @FilterScope scope: Int, allowedKeywords: Array? = null): Boolean { + val query = isFilteredQuery(users, texts, sources, links, names, descriptions, true, + scope, allowedKeywords) + val cur = cr.rawQuery(query.first, query.second) ?: return false + @Suppress("ConvertTryFinallyToUseCall") + try { + return cur.moveToFirst() && cur.getInt(0) != 0 + } finally { + cur.close() + } + } + + fun isFilteredQuery(users: Array?, texts: String?, sources: Array?, + links: Array?, names: Array?, descriptions: String?, + filterRts: Boolean, @FilterScope scope: Int, allowedKeywords: Array? = null): Pair> { + val selectionArgs = mutableListOf() + val queryBuilder = StringBuilder("SELECT ") + + fun addExpression(ruleTable: String, ruleField: String, scopeField: String, + @FilterScope expressionScope: Int, noScopeAsTrue: Boolean, matchType: Int, + value: String, extraWhereAppend: ((StringBuilder, MutableList, String) -> Unit)? = null) { + if (selectionArgs.isNotEmpty()) { + queryBuilder.append(" OR ") + } + selectionArgs.add(value) + queryBuilder.append("1 IN (SELECT ? LIKE ") + when (matchType) { + LINE_MATCH -> { + queryBuilder.append("'%\\'||") + queryBuilder.append(ruleField) + queryBuilder.append("||'\\%'") + } + LINE_CONTAINS -> { + queryBuilder.append("'\\%'||") + queryBuilder.append(ruleField) + queryBuilder.append("||'%\\'") + } + CONTAINS -> { + queryBuilder.append("'%'||") + queryBuilder.append(ruleField) + queryBuilder.append("||'%'") + } + } + queryBuilder.append(" FROM ") + queryBuilder.append(ruleTable) + queryBuilder.append(" WHERE ") + if (noScopeAsTrue) { + queryBuilder.append("(") + queryBuilder.append(scopeField) + queryBuilder.append(" = 0 OR ") + } + queryBuilder.append(scopeField) + queryBuilder.append(" & ") + queryBuilder.append(expressionScope) + queryBuilder.append(" != 0") + if (noScopeAsTrue) { + queryBuilder.append(")") + } + extraWhereAppend?.invoke(queryBuilder, selectionArgs, ruleField) + queryBuilder.append(")") + + } + + fun allowKeywordsWhere(sb: StringBuilder, args: MutableList, ruleField: String) { + val allowed = allowedKeywords?.takeUnless(Array<*>::isEmpty) ?: return + sb.append(" AND NOT (") + allowed.forEachIndexed { i, s -> + args.add(s) + if (i != 0) { + sb.append(" OR ") + } + sb.append("? LIKE ") + sb.append(ruleField) + } + sb.append(")") + } + + if (users != null) { + addExpression(Users.TABLE_NAME, Users.USER_KEY, Users.SCOPE, scope, true, + LINE_MATCH, users.joinToString("\n", "\\", "\\")) + } + if (sources != null) { + addExpression(Sources.TABLE_NAME, Sources.VALUE, Sources.SCOPE, scope, true, + LINE_MATCH, sources.joinToString("\n", "\\", "\\")) + } + if (links != null) { + addExpression(Links.TABLE_NAME, Links.VALUE, Links.SCOPE, scope, true, + LINE_CONTAINS, links.joinToString("\n", "\\", "\\")) + } + if (texts != null) { + addExpression(Keywords.TABLE_NAME, Keywords.VALUE, Keywords.SCOPE, + FilterScope.TARGET_TEXT or scope, true, CONTAINS, + texts, ::allowKeywordsWhere) + } + if (names != null) { + addExpression(Keywords.TABLE_NAME, Keywords.VALUE, Keywords.SCOPE, + FilterScope.TARGET_NAME or scope, false, LINE_CONTAINS, + names.joinToString("\n", "\\", "\\"), ::allowKeywordsWhere) + } + if (descriptions != null) { + addExpression(Keywords.TABLE_NAME, Keywords.VALUE, Keywords.SCOPE, + FilterScope.TARGET_DESCRIPTION or scope, false, CONTAINS, + descriptions, ::allowKeywordsWhere) + } + if (queryBuilder.isEmpty()) + return Pair("SELECT 0", emptyArray()) + return Pair(queryBuilder.toString(), selectionArgs.toTypedArray()) + } + + private const val LINE_MATCH = 0 + private const val LINE_CONTAINS = 1 + private const val CONTAINS = 2 + +} diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/database/FilterQueryBuilder.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/database/FilterQueryBuilder.kt deleted file mode 100644 index bc56d0ca7..000000000 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/util/database/FilterQueryBuilder.kt +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Twidere - Twitter client for Android - * - * Copyright (C) 2012-2017 Mariotaku Lee - * - * 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 . - */ - -package org.mariotaku.twidere.util.database - -import android.content.ContentResolver -import org.mariotaku.twidere.extension.rawQuery -import org.mariotaku.twidere.model.ParcelableStatus -import org.mariotaku.twidere.model.SpanItem -import org.mariotaku.twidere.model.UserKey -import org.mariotaku.twidere.provider.TwidereDataStore.Filters -import java.util.* - -/** - * Created by mariotaku on 2017/2/16. - */ - -object FilterQueryBuilder { - - fun isFiltered(cr: ContentResolver, activity: ParcelableStatus): Boolean { - return isFiltered(cr, activity.user_key, activity.text_plain, - activity.quoted_text_plain, activity.spans, activity.quoted_spans, - activity.source, activity.quoted_source, activity.retweeted_by_user_key, - activity.quoted_user_key) - } - - fun isFiltered(cr: ContentResolver, userKey: UserKey?, textPlain: String?, quotedTextPlain: String?, - spans: Array?, quotedSpans: Array?, source: String?, quotedSource: String?, - retweetedByKey: UserKey?, quotedUserKey: UserKey?): Boolean { - val query = FilterQueryBuilder.isFilteredQuery(userKey, - textPlain, quotedTextPlain, spans, quotedSpans, source, quotedSource, retweetedByKey, - quotedUserKey, true) - val cur = cr.rawQuery(query.first, query.second) ?: return false - @Suppress("ConvertTryFinallyToUseCall") - try { - return cur.moveToFirst() && cur.getInt(0) != 0 - } finally { - cur.close() - } - } - - fun isFilteredQuery(userKey: UserKey?, textPlain: String?, quotedTextPlain: String?, - spans: Array?, quotedSpans: Array?, source: String?, quotedSource: String?, - retweetedByKey: UserKey?, quotedUserKey: UserKey?, filterRts: Boolean): Pair> { - val builder = StringBuilder() - val selectionArgs = ArrayList() - builder.append("SELECT ") - if (textPlain != null) { - selectionArgs.add(textPlain) - addTextPlainStatement(builder) - } - if (quotedTextPlain != null) { - if (!selectionArgs.isEmpty()) { - builder.append(" OR ") - } - selectionArgs.add(quotedTextPlain) - addTextPlainStatement(builder) - } - if (spans != null) { - if (!selectionArgs.isEmpty()) { - builder.append(" OR ") - } - addSpansStatement(spans, builder, selectionArgs) - } - if (quotedSpans != null) { - if (!selectionArgs.isEmpty()) { - builder.append(" OR ") - } - addSpansStatement(quotedSpans, builder, selectionArgs) - } - if (userKey != null) { - if (!selectionArgs.isEmpty()) { - builder.append(" OR ") - } - selectionArgs.add(userKey.toString()) - createUserKeyStatement(builder) - } - if (retweetedByKey != null) { - if (!selectionArgs.isEmpty()) { - builder.append(" OR ") - } - selectionArgs.add(retweetedByKey.toString()) - createUserKeyStatement(builder) - } - if (quotedUserKey != null) { - if (!selectionArgs.isEmpty()) { - builder.append(" OR ") - } - selectionArgs.add(quotedUserKey.toString()) - createUserKeyStatement(builder) - } - if (source != null) { - if (!selectionArgs.isEmpty()) { - builder.append(" OR ") - } - selectionArgs.add(source) - appendSourceStatement(builder) - } - if (quotedSource != null) { - if (!selectionArgs.isEmpty()) { - builder.append(" OR ") - } - selectionArgs.add(quotedSource) - appendSourceStatement(builder) - } - return Pair(builder.toString(), selectionArgs.toTypedArray()) - } - - - private fun createUserKeyStatement(builder: StringBuilder) { - builder.append("(SELECT ") - .append("?") - .append(" IN (SELECT ") - .append(Filters.Users.USER_KEY) - .append(" FROM ") - .append(Filters.Users.TABLE_NAME) - .append("))") - } - - private fun appendSourceStatement(builder: StringBuilder) { - builder.append("(SELECT 1 IN (SELECT ? LIKE '%>'||") - .append(Filters.Sources.TABLE_NAME).append(".") - .append(Filters.VALUE).append("||'%' FROM ") - .append(Filters.Sources.TABLE_NAME) - .append("))") - } - - private fun addTextPlainStatement(builder: StringBuilder) { - builder.append("(SELECT 1 IN (SELECT ? LIKE '%'||") - .append(Filters.Keywords.TABLE_NAME) - .append(".") - .append(Filters.VALUE) - .append("||'%' FROM ") - .append(Filters.Keywords.TABLE_NAME) - .append("))") - } - - private fun addSpansStatement(spans: Array, builder: StringBuilder, selectionArgs: MutableList) { - val spansFlat = StringBuilder() - for (span in spans) { - spansFlat.append(span.link) - spansFlat.append(' ') - } - selectionArgs.add(spansFlat.toString()) - builder.append("(SELECT 1 IN (SELECT ? LIKE '%'||") - .append(Filters.Links.TABLE_NAME) - .append(".") - .append(Filters.VALUE) - .append("||'%' FROM ") - .append(Filters.Links.TABLE_NAME) - .append("))") - } - -} diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/notification/ContentNotificationManager.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/notification/ContentNotificationManager.kt index 1059a1e1f..330cc1bcf 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/util/notification/ContentNotificationManager.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/notification/ContentNotificationManager.kt @@ -62,7 +62,7 @@ import org.mariotaku.twidere.receiver.NotificationReceiver import org.mariotaku.twidere.service.LengthyOperationsService import org.mariotaku.twidere.util.* import org.mariotaku.twidere.util.Utils -import org.mariotaku.twidere.util.database.FilterQueryBuilder +import org.mariotaku.twidere.util.database.ContentFiltersUtils import org.oshkimaadziig.george.androidutils.SpanFormatter class ContentNotificationManager( @@ -219,7 +219,7 @@ class ContentNotificationManager( if (pref.isNotificationMentionsOnly && activity.action !in Activity.Action.MENTION_ACTIONS) { return@forEachRow false } - if (FilterQueryBuilder.isFiltered(cr, activity)) { + if (ContentFiltersUtils.isFiltered(cr, activity, true, FilterScope.INTERACTIONS)) { return@forEachRow false } val sources = ParcelableActivityUtils.filterSources(activity.sources_lite,