added initial filter support

This commit is contained in:
nuclearfog 2023-06-18 22:24:39 +02:00
parent 7e92f8cac5
commit 408b6009f8
No known key found for this signature in database
GPG Key ID: 03488A185C476379
6 changed files with 620 additions and 0 deletions

View File

@ -2,12 +2,14 @@ package org.nuclearfog.twidda.backend.api;
import org.nuclearfog.twidda.backend.helper.ConnectionConfig;
import org.nuclearfog.twidda.backend.helper.MediaStatus;
import org.nuclearfog.twidda.backend.helper.update.FilterUpdate;
import org.nuclearfog.twidda.backend.helper.update.ProfileUpdate;
import org.nuclearfog.twidda.backend.helper.update.PushUpdate;
import org.nuclearfog.twidda.backend.helper.update.StatusUpdate;
import org.nuclearfog.twidda.backend.helper.update.UserListUpdate;
import org.nuclearfog.twidda.model.Account;
import org.nuclearfog.twidda.model.Emoji;
import org.nuclearfog.twidda.model.Filter;
import org.nuclearfog.twidda.model.Instance;
import org.nuclearfog.twidda.model.Location;
import org.nuclearfog.twidda.model.Notification;
@ -641,6 +643,27 @@ public interface Connection {
*/
List<Long> getIdBlocklist() throws ConnectionException;
/**
* returns used filter
*
* @return list of filter
*/
List<Filter> getFilter() throws ConnectionException;
/**
* create/update status filter
*
* @param update filter to update
*/
void updateFilter(FilterUpdate update) throws ConnectionException;
/**
* delete status filter
*
* @param id ID of the filter to delete
*/
void deleteFilter(long id) throws ConnectionException;
/**
* download image
*

View File

@ -15,6 +15,7 @@ import org.nuclearfog.twidda.backend.api.Connection;
import org.nuclearfog.twidda.backend.api.ConnectionException;
import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonAccount;
import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonEmoji;
import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonFilter;
import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonInstance;
import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonList;
import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonNotification;
@ -27,6 +28,7 @@ import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonTrend;
import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonUser;
import org.nuclearfog.twidda.backend.helper.ConnectionConfig;
import org.nuclearfog.twidda.backend.helper.MediaStatus;
import org.nuclearfog.twidda.backend.helper.update.FilterUpdate;
import org.nuclearfog.twidda.backend.helper.update.PollUpdate;
import org.nuclearfog.twidda.backend.helper.update.ProfileUpdate;
import org.nuclearfog.twidda.backend.helper.update.PushUpdate;
@ -37,6 +39,7 @@ import org.nuclearfog.twidda.backend.utils.StringUtils;
import org.nuclearfog.twidda.config.GlobalSettings;
import org.nuclearfog.twidda.model.Account;
import org.nuclearfog.twidda.model.Emoji;
import org.nuclearfog.twidda.model.Filter;
import org.nuclearfog.twidda.model.Instance;
import org.nuclearfog.twidda.model.Location;
import org.nuclearfog.twidda.model.Notification;
@ -68,6 +71,7 @@ import java.security.spec.ECPoint;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
@ -140,6 +144,7 @@ public class Mastodon implements Connection {
private static final String ENDPOINT_POLL = "/api/v1/polls/";
private static final String ENDPOINT_DOMAIN_BLOCK = "/api/v1/domain_blocks";
private static final String ENDPOINT_PUSH_UPDATE = "/api/v1/push/subscription";
private static final String ENDPOINT_FILTER = "/api/v2/filters";
private static final MediaType TYPE_TEXT = MediaType.parse("text/plain");
private static final MediaType TYPE_STREAM = MediaType.parse("application/octet-stream");
@ -941,6 +946,80 @@ public class Mastodon implements Connection {
}
@Override
public List<Filter> getFilter() throws ConnectionException {
try {
Response response = get(ENDPOINT_FILTER, new ArrayList<>());
ResponseBody body = response.body();
if (response.code() == 200 && body != null) {
JSONArray array = new JSONArray(body.string());
List<Filter> result = new LinkedList<>();
for (int i = 0 ; i < array.length(); i++) {
result.add(new MastodonFilter(array.getJSONObject(i)));
}
return result;
}
throw new MastodonException(response);
} catch (IOException | JSONException exception) {
throw new MastodonException(exception);
}
}
@Override
public void updateFilter(FilterUpdate update) throws ConnectionException {
try {
List<String> params = new ArrayList<>();
params.add("title=" + update.getTitle());
if (update.getExpirationTime() > 0)
params.add("expires_in=" + update.getExpirationTime());
if (update.filterHomeSet())
params.add("context[]=home");
if (update.filterNotificationSet())
params.add("context[]=notifications");
if (update.filterPublicSet())
params.add("context[]=public");
if (update.filterThreadSet())
params.add("context[]=thread");
if (update.filterUserSet())
params.add("context[]=account");
if (update.getFilterAction() == Filter.ACTION_WARN)
params.add("filter_action=warn");
else if (update.getFilterAction() == Filter.ACTION_HIDE)
params.add("filter_action=hide");
if (update.wholeWord())
params.add("keywords_attributes[][whole_word]=true");
else
params.add("keywords_attributes[][whole_word]=false");
for (String keyword : update.getKeywords())
params.add("keywords_attributes[][keyword]=" + StringUtils.encode(keyword));
Response response;
if (update.getId() != 0L)
response = put(ENDPOINT_FILTER + '/' + update.getId(), params);
else
response = post(ENDPOINT_FILTER, params);
if (response.code() != 200) {
throw new MastodonException(response);
}
} catch (IOException exception) {
throw new MastodonException(exception);
}
}
@Override
public void deleteFilter(long id) throws ConnectionException {
try {
Response response = delete(ENDPOINT_FILTER + '/' + id, new ArrayList<>());
if (response.code() != 200) {
throw new MastodonException(response);
}
} catch (IOException exception) {
throw new MastodonException(exception);
}
}
@Override
public MediaStatus downloadImage(String link) throws MastodonException {
try {

View File

@ -0,0 +1,185 @@
package org.nuclearfog.twidda.backend.api.mastodon.impl;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.nuclearfog.twidda.backend.utils.StringUtils;
import org.nuclearfog.twidda.model.Filter;
/**
* Mastodon implementation of a status filter
*
* @author nuclearfog
*/
public class MastodonFilter implements Filter {
private static final long serialVersionUID = -3363900731940590588L;
private long id;
private long expiresAt = 0L;
private String title;
private Keyword[] keywords;
private int action;
private boolean filterHome, filterNotification, filterPublic, filterUser, filterThread;
/**
* @param json Mastodon Filter json
*/
public MastodonFilter(JSONObject json) throws JSONException {
JSONArray typeArray = json.getJSONArray("context");
JSONArray keywordArray = json.getJSONArray("keywords");
String idStr = json.getString("id");
String actionStr = json.getString("filter_action");
String expiresStr = json.optString("expires_at");
title = json.getString("title");
switch (actionStr) {
default:
case "warn":
action = ACTION_WARN;
break;
case "hide":
action = ACTION_HIDE;
break;
}
for (int i = 0 ; i < typeArray.length() ; i++) {
switch (typeArray.getString(i)) {
case "home":
filterHome = true;
break;
case "notifications":
filterNotification = true;
break;
case "public":
filterPublic = true;
break;
case "account":
filterUser = true;
break;
case "thread":
filterThread = true;
break;
}
}
keywords = new Keyword[keywordArray.length()];
for (int i = 0 ; i < keywordArray.length() ; i++) {
keywords[i] = new MastodonKeyword(keywordArray.getJSONObject(i));
}
if (!expiresStr.equals("null")) {
expiresAt = StringUtils.getTime(expiresStr, StringUtils.TIME_MASTODON);
}
try {
id = Long.parseLong(idStr);
} catch (NumberFormatException exception) {
throw new JSONException("Bad ID: " + idStr);
}
}
@Override
public long getId() {
return id;
}
@Override
public String getTitle() {
return title;
}
@Override
public long getExpirationTime() {
return expiresAt;
}
@Override
public Keyword[] getKeywords() {
return keywords;
}
@Override
public int getAction() {
return action;
}
@Override
public boolean filterHome() {
return filterHome;
}
@Override
public boolean filterNotifications() {
return filterNotification;
}
@Override
public boolean filterPublic() {
return filterPublic;
}
@Override
public boolean filterThreads() {
return filterThread;
}
@Override
public boolean filterUserTimeline() {
return filterUser;
}
/**
*
*/
private static class MastodonKeyword implements Keyword {
private static final long serialVersionUID = -2619670483101168015L;
private long id;
private String keyword;
private boolean oneWord;
/**
* @param json json object containing filter keyword
*/
private MastodonKeyword(JSONObject json) throws JSONException {
String idStr = json.getString("id");
keyword = json.getString("keyword");
oneWord = json.optBoolean("whole_word", false);
try {
id = Long.parseLong(idStr);
} catch (NumberFormatException exception) {
throw new JSONException("Bad ID: " + idStr);
}
}
@Override
public long getId() {
return id;
}
@Override
public String getKeyword() {
return keyword;
}
@Override
public boolean isOneWord() {
return oneWord;
}
}
}

View File

@ -24,6 +24,7 @@ import org.nuclearfog.twidda.backend.api.twitter.v1.impl.UserListV1;
import org.nuclearfog.twidda.backend.api.twitter.v1.impl.UserV1;
import org.nuclearfog.twidda.backend.helper.ConnectionConfig;
import org.nuclearfog.twidda.backend.helper.MediaStatus;
import org.nuclearfog.twidda.backend.helper.update.FilterUpdate;
import org.nuclearfog.twidda.backend.helper.update.ProfileUpdate;
import org.nuclearfog.twidda.backend.helper.update.PushUpdate;
import org.nuclearfog.twidda.backend.helper.update.StatusUpdate;
@ -34,6 +35,7 @@ import org.nuclearfog.twidda.config.GlobalSettings;
import org.nuclearfog.twidda.database.AppDatabase;
import org.nuclearfog.twidda.model.Account;
import org.nuclearfog.twidda.model.Emoji;
import org.nuclearfog.twidda.model.Filter;
import org.nuclearfog.twidda.model.Instance;
import org.nuclearfog.twidda.model.Location;
import org.nuclearfog.twidda.model.Notification;
@ -1190,6 +1192,24 @@ public class TwitterV1 implements Connection {
}
@Override
public List<Filter> getFilter() throws ConnectionException {
throw new TwitterException("not implemented!");
}
@Override
public void updateFilter(FilterUpdate update) throws ConnectionException {
throw new TwitterException("not implemented!");
}
@Override
public void deleteFilter(long id) throws ConnectionException {
throw new TwitterException("not implemented!");
}
@Override
public Notifications getNotifications(long minId, long maxId) throws TwitterException {
List<Status> mentions = getTweets(TWEETS_MENTIONS, new ArrayList<>(), minId, maxId);

View File

@ -0,0 +1,207 @@
package org.nuclearfog.twidda.backend.helper.update;
import org.nuclearfog.twidda.model.Filter;
import java.io.Serializable;
/**
* Filter update class used to create or update an existing status filter
*
* @author nuclearfog
*/
public class FilterUpdate implements Serializable {
private static final long serialVersionUID = 7408688572155707380L;
private long id = 0L;
private String title;
private String[] keywords = {};
private int expires_at = 0, action = Filter.ACTION_WARN;
private boolean filterHome, filterNotification, filterPublic, filterUser, filterThread, wholeWord;
/**
* filter ID of an existing filter or '0' if a new filter should be created
*
* @return filter ID
*/
public long getId() {
return id;
}
/**
* set filter ID of an existing filter
*
* @param id filter ID
*/
public void setId(long id) {
this.id = id;
}
/**
* get filter title
*
* @return title
*/
public String getTitle() {
return title;
}
/**
* set title of the filter
*
* @param title title (description)
*/
public void setTitle(String title) {
this.title = title;
}
/**
* get time to expiration in seconds
*
* @return expiration time
*/
public int getExpirationTime() {
return expires_at;
}
/**
* set time to expire
*
* @param expires_at time until filter expires in seconds
*/
public void setExpirationTime(int expires_at) {
this.expires_at = expires_at;
}
/**
* get an array of keywords to filter
*
* @return array of keywords
*/
public String[] getKeywords() {
return keywords;
}
/**
* add keywords of the filter
*
* @param keywords array of keywords
*/
public void setKeywords(String[] keywords) {
this.keywords = keywords.clone();
}
/**
* get filter action
*
* @return filter action {@link Filter#ACTION_WARN,Filter#ACTION_HIDE}
*/
public int getFilterAction() {
return action;
}
/**
* set filter action
*
* @param action filter action {@link Filter#ACTION_WARN,Filter#ACTION_HIDE}
*/
public void setFilterAction(int action) {
this.action = action;
}
/**
* @return true if filter is set for home timeline
*/
public boolean filterHomeSet() {
return filterHome;
}
/**
* enable/disable filter for home timeline
*
* @param filterHome true to enable filter
*/
public void setFilterHome(boolean filterHome) {
this.filterHome = filterHome;
}
/**
* @return true if filter is set for notifications
*/
public boolean filterNotificationSet() {
return filterNotification;
}
/**
* enable/disable notification filter
*
* @param filterNotification true to enable filter
*/
public void setFilterNotification(boolean filterNotification) {
this.filterNotification = filterNotification;
}
/**
* @return true if filter is set for public timeline
*/
public boolean filterPublicSet() {
return filterPublic;
}
/**
* enable/disable filter for public timeline
*
* @param filterPublic true to enable filter
*/
public void setFilterPublic(boolean filterPublic) {
this.filterPublic = filterPublic;
}
/**
* @return true if filter is set for user timeline
*/
public boolean filterUserSet() {
return filterUser;
}
/**
* enable/disable filter for user timeline
*
* @param filterUser true to enable filter
*/
public void setFilterUser(boolean filterUser) {
this.filterUser = filterUser;
}
/**
* @return true if filter is set for threads
*/
public boolean filterThreadSet() {
return filterThread;
}
/**
* enable/disable filter for threads
*
* @param filterThread true to enable filter
*/
public void setFilterThread(boolean filterThread) {
this.filterThread = filterThread;
}
/**
* check if words of a single keyword should be interpreted as one word
*
* @return true if keyword should be interpreted as one word
*/
public boolean wholeWord() {
return wholeWord;
}
/**
* enable/disable option to interpret words of a keyword as a single word
*/
public void setWholeWord(boolean wholeWord) {
this.wholeWord = wholeWord;
}
}

View File

@ -0,0 +1,106 @@
package org.nuclearfog.twidda.model;
import java.io.Serializable;
/**
* Status filter interface used to filter status containing words from timelines
*
* @author nuclearfog
*/
public interface Filter extends Serializable {
/**
* warn on filter match
*/
int ACTION_WARN = 1;
/**
* hide status on filter match
*/
int ACTION_HIDE = 2;
/**
* get filter ID
*
* @return filter ID
*/
long getId();
/**
* get title of the filter
*
* @return title string
*/
String getTitle();
/**
* get date time where the filter expires
*
* @return date time or '0' if not defined
*/
long getExpirationTime();
/**
* get an array of keywords to filter
*
* @return array of keywords
*/
Keyword[] getKeywords();
/**
* get action to take when filtering a status
*
* @return action type {@link #ACTION_HIDE,#ACTION_WARN}
*/
int getAction();
/**
* @return true to filter home timeline
*/
boolean filterHome();
/**
* @return true to filter notification timeline
*/
boolean filterNotifications();
/**
* @return true to filter public timelines
*/
boolean filterPublic();
/**
* @return true to apply filter at threads
*/
boolean filterThreads();
/**
* @return true to apply filter at user timelines
*/
boolean filterUserTimeline();
/**
* Filter keyword used to filter statuses from timeline containing one of these words
*/
interface Keyword extends Serializable {
/**
* get keyword ID
*
* @return ID
*/
long getId();
/**
* get used keyword
*
* @return keyword text
*/
String getKeyword();
/**
* @return true if single words should be interpreted as one word
*/
boolean isOneWord();
}
}