fedilab-Android-App/app/src/main/java/app/fedilab/android/mastodon/helper/TimelineHelper.java

314 lines
16 KiB
Java

package app.fedilab.android.mastodon.helper;
/* Copyright 2022 Thomas Schneider
*
* This file is a part of Fedilab
*
* 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.
*
* Fedilab 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 Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.BaseMainActivity.filteredAccounts;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.text.Html;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStoreOwner;
import androidx.preference.PreferenceManager;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.mastodon.client.endpoints.MastodonFiltersService;
import app.fedilab.android.mastodon.client.entities.api.Account;
import app.fedilab.android.mastodon.client.entities.api.Filter;
import app.fedilab.android.mastodon.client.entities.api.Notification;
import app.fedilab.android.mastodon.client.entities.api.Status;
import app.fedilab.android.mastodon.client.entities.app.Timeline;
import app.fedilab.android.mastodon.viewmodel.mastodon.FiltersVM;
import okhttp3.OkHttpClient;
import retrofit2.Call;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class TimelineHelper {
private static MastodonFiltersService initv2(Context context) {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(60, TimeUnit.SECONDS)
.connectTimeout(60, TimeUnit.SECONDS)
.proxy(Helper.getProxy(context))
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://" + BaseMainActivity.currentInstance + "/api/v2/")
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();
return retrofit.create(MastodonFiltersService.class);
}
/**
* Allows to filter statuses, should be called in API calls (background)
*
* @param context - Context
* @param statuses - List of {@link Status}
* @param filterTimeLineType - {@link Timeline.TimeLineEnum}
* @return filtered List<Status>
*/
public static List<Status> filterStatus(Context context, List<Status> statuses, Timeline.TimeLineEnum filterTimeLineType) {
//A security to make sure filters have been fetched before displaying messages
if (!BaseMainActivity.filterFetched) {
MastodonFiltersService mastodonFiltersService = initv2(context);
List<Filter> filterList;
Call<List<Filter>> getFiltersCall = mastodonFiltersService.getFilters(BaseMainActivity.currentToken);
if (getFiltersCall != null) {
try {
Response<List<Filter>> getFiltersResponse = getFiltersCall.execute();
if (getFiltersResponse.isSuccessful()) {
BaseMainActivity.filterFetched = true;
filterList = getFiltersResponse.body();
BaseMainActivity.mainFilters = filterList;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//If there are filters:
if (BaseMainActivity.mainFilters != null && BaseMainActivity.mainFilters.size() > 0 && statuses != null && statuses.size() > 0) {
//Loop through filters
for (Filter filter : BaseMainActivity.mainFilters) {
if (filter.expires_at != null && filter.expires_at.before(new Date())) {
//Expired filter
continue;
}
if (filterTimeLineType == Timeline.TimeLineEnum.HOME || filterTimeLineType == Timeline.TimeLineEnum.LIST) {
if (!filter.context.contains("home")) continue;
} else if (filterTimeLineType == Timeline.TimeLineEnum.NOTIFICATION) {
if (!filter.context.contains("notifications")) continue;
} else if (filterTimeLineType == Timeline.TimeLineEnum.CONTEXT) {
if (!filter.context.contains("thread")) continue;
} else if (filterTimeLineType == Timeline.TimeLineEnum.ACCOUNT_TIMELINE) {
if (!filter.context.contains("account")) continue;
} else {
if (!filter.context.contains("public")) continue;
}
if (filter.keywords != null && filter.keywords.size() > 0) {
for (Filter.KeywordsAttributes filterKeyword : filter.keywords) {
String sb = Pattern.compile("\\A[A-Za-z0-9_]").matcher(filterKeyword.keyword).find() ? "\\b" : "";
String eb = Pattern.compile("[A-Za-z0-9_]\\z").matcher(filterKeyword.keyword).find() ? "\\b" : "";
Pattern p;
if (filterKeyword.whole_word) {
p = Pattern.compile(sb + "(" + Pattern.quote(filterKeyword.keyword) + ")" + eb, Pattern.CASE_INSENSITIVE);
} else {
p = Pattern.compile("(" + Pattern.quote(filterKeyword.keyword) + ")", Pattern.CASE_INSENSITIVE);
}
for (Status status : statuses) {
if (status.account.id.equals(MainActivity.currentUserID)) {
continue;
}
String content;
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
content = Html.fromHtml(status.reblog != null ? status.reblog.content : status.content, Html.FROM_HTML_MODE_LEGACY).toString();
else
content = Html.fromHtml(status.reblog != null ? status.reblog.content : status.content).toString();
} catch (Exception e) {
content = status.reblog != null ? status.reblog.content : status.content;
}
Matcher m = p.matcher(content);
if (m.find()) {
status.filteredByApp = filter;
continue;
}
if (status.spoiler_text != null) {
String spoilerText;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
spoilerText = Html.fromHtml(status.reblog != null ? status.reblog.spoiler_text : status.spoiler_text, Html.FROM_HTML_MODE_LEGACY).toString();
else
spoilerText = Html.fromHtml(status.reblog != null ? status.reblog.spoiler_text : status.spoiler_text).toString();
Matcher ms = p.matcher(spoilerText);
if (ms.find()) {
status.filteredByApp = filter;
}
}
}
}
}
}
}
if (statuses != null && statuses.size() > 0) {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
boolean groupReblogs = sharedpreferences.getBoolean(context.getString(R.string.SET_GROUP_REBLOGS), true);
if (filterTimeLineType == Timeline.TimeLineEnum.HOME) {
for (int i = 0; i < statuses.size(); i++) {
if (filteredAccounts != null && filteredAccounts.size() > 0) {
for (Account account : filteredAccounts) {
if (account.acct.equals(statuses.get(i).account.acct) || (statuses.get(i).reblog != null && account.acct.equals(statuses.get(i).reblog.account.acct))) {
Filter filterCustom = new Filter();
filterCustom.filter_action = "hide";
ArrayList<String> contextCustom = new ArrayList<>();
contextCustom.add("home");
filterCustom.title = "Fedilab";
filterCustom.context = contextCustom;
statuses.get(i).filteredByApp = filterCustom;
break;
}
}
}
//Group boosts
if (groupReblogs && statuses.get(i).filteredByApp == null && statuses.get(i).reblog != null) {
for (int j = 0; j < i; j++) {
if (statuses.get(j).reblog != null && statuses.get(j).reblog.id.equals(statuses.get(i).reblog.id)) {
Filter filterCustom = new Filter();
filterCustom.filter_action = "hide";
ArrayList<String> contextCustom = new ArrayList<>();
contextCustom.add("home");
filterCustom.title = "Fedilab reblog";
filterCustom.context = contextCustom;
statuses.get(i).filteredByApp = filterCustom;
}
}
}
}
}
}
return statuses;
}
/**
* Allows to filter notifications, should be called in API calls (background)
*
* @param context - Context
* @param notifications - List of {@link Notification}
* @return filtered List<Status>
*/
public static List<Notification> filterNotification(Context context, List<Notification> notifications) {
//A security to make sure filters have been fetched before displaying messages
List<Notification> notificationToRemove = new ArrayList<>();
if (!BaseMainActivity.filterFetched) {
try {
FiltersVM filtersVM = new ViewModelProvider((ViewModelStoreOwner) context).get(FiltersVM.class);
filtersVM.getFilters(BaseMainActivity.currentInstance, BaseMainActivity.currentToken).observe((LifecycleOwner) context, filters -> {
BaseMainActivity.filterFetched = true;
BaseMainActivity.mainFilters = filters;
});
} catch (Exception e) {
return notifications;
}
}
//If there are filters:
if (BaseMainActivity.mainFilters != null && BaseMainActivity.mainFilters.size() > 0 && notifications != null && notifications.size() > 0) {
//Loop through filters
for (Filter filter : BaseMainActivity.mainFilters) {
if (filter.expires_at != null && filter.expires_at.before(new Date())) {
//Expired filter
continue;
}
if (!filter.context.contains("notifications")) continue;
if (filter.keywords != null && filter.keywords.size() > 0) {
for (Filter.KeywordsAttributes filterKeyword : filter.keywords) {
String sb = Pattern.compile("\\A[A-Za-z0-9_]").matcher(filterKeyword.keyword).find() ? "\\b" : "";
String eb = Pattern.compile("[A-Za-z0-9_]\\z").matcher(filterKeyword.keyword).find() ? "\\b" : "";
Pattern p;
if (filterKeyword.whole_word) {
p = Pattern.compile(sb + "(" + Pattern.quote(filterKeyword.keyword) + ")" + eb, Pattern.CASE_INSENSITIVE);
} else {
p = Pattern.compile("(" + Pattern.quote(filterKeyword.keyword) + ")", Pattern.CASE_INSENSITIVE);
}
for (Notification notification : notifications) {
if (notification.status == null) {
continue;
}
if (notification.status.account.id.equals(MainActivity.currentUserID)) {
continue;
}
String content;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
content = Html.fromHtml(notification.status.reblog != null ? notification.status.reblog.content : notification.status.content, Html.FROM_HTML_MODE_LEGACY).toString();
else
content = Html.fromHtml(notification.status.reblog != null ? notification.status.reblog.content : notification.status.content).toString();
Matcher m = p.matcher(content);
if (m.find()) {
if (filter.filter_action.equalsIgnoreCase("warn")) {
notification.filteredByApp = filter;
} else {
notificationToRemove.add(notification);
}
continue;
}
if (notification.status.spoiler_text != null) {
String spoilerText;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
spoilerText = Html.fromHtml(notification.status.reblog != null ? notification.status.reblog.spoiler_text : notification.status.spoiler_text, Html.FROM_HTML_MODE_LEGACY).toString();
else
spoilerText = Html.fromHtml(notification.status.reblog != null ? notification.status.reblog.spoiler_text : notification.status.spoiler_text).toString();
Matcher ms = p.matcher(spoilerText);
if (ms.find()) {
if (filter.filter_action.equalsIgnoreCase("warn")) {
notification.filteredByApp = filter;
} else {
notificationToRemove.add(notification);
}
}
}
}
}
}
}
}
if (notifications != null) {
notifications.removeAll(notificationToRemove);
}
return notifications;
}
/**
* Check if WIFI is opened
*
* @param context Context
* @return boolean
*/
public static boolean isOnWIFI(Context context) {
ConnectivityManager connManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connManager != null) {
NetworkInfo activeNetwork = connManager.getActiveNetworkInfo();
return (activeNetwork != null && activeNetwork.getType() == ConnectivityManager.TYPE_WIFI);
}
return false;
}
}