kopia lustrzana https://gitlab.com/mysocialportal/relatica
Add domain blocking capability to low level filters
rodzic
ed1b800e82
commit
5eb7dcf7fe
|
@ -1,6 +1,7 @@
|
||||||
enum ComparisonType {
|
enum ComparisonType {
|
||||||
containsCaseSensitive,
|
contains,
|
||||||
containsCaseInsensitive,
|
containsIgnoreCase,
|
||||||
|
endsWithIgnoreCase,
|
||||||
equals,
|
equals,
|
||||||
equalsIgnoreCase,
|
equalsIgnoreCase,
|
||||||
;
|
;
|
||||||
|
|
|
@ -18,6 +18,7 @@ class TimelineEntryFilter {
|
||||||
final TimelineEntryFilterAction action;
|
final TimelineEntryFilterAction action;
|
||||||
final String name;
|
final String name;
|
||||||
final List<StringFilter> authorFilters;
|
final List<StringFilter> authorFilters;
|
||||||
|
final List<StringFilter> domainFilters;
|
||||||
final List<StringFilter> contentFilters;
|
final List<StringFilter> contentFilters;
|
||||||
final List<StringFilter> hashtagFilters;
|
final List<StringFilter> hashtagFilters;
|
||||||
|
|
||||||
|
@ -25,6 +26,7 @@ class TimelineEntryFilter {
|
||||||
required this.action,
|
required this.action,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.authorFilters,
|
required this.authorFilters,
|
||||||
|
required this.domainFilters,
|
||||||
required this.contentFilters,
|
required this.contentFilters,
|
||||||
required this.hashtagFilters,
|
required this.hashtagFilters,
|
||||||
});
|
});
|
||||||
|
@ -33,6 +35,7 @@ class TimelineEntryFilter {
|
||||||
required TimelineEntryFilterAction action,
|
required TimelineEntryFilterAction action,
|
||||||
required String name,
|
required String name,
|
||||||
List<Connection> authors = const [],
|
List<Connection> authors = const [],
|
||||||
|
List<String> domains = const [],
|
||||||
List<String> keywords = const [],
|
List<String> keywords = const [],
|
||||||
List<String> hashtags = const [],
|
List<String> hashtags = const [],
|
||||||
}) {
|
}) {
|
||||||
|
@ -43,9 +46,20 @@ class TimelineEntryFilter {
|
||||||
.map((a) =>
|
.map((a) =>
|
||||||
StringFilter(filterString: a.id, type: ComparisonType.equals))
|
StringFilter(filterString: a.id, type: ComparisonType.equals))
|
||||||
.toList(),
|
.toList(),
|
||||||
|
domainFilters: domains
|
||||||
|
.map((d) => d.startsWith('*')
|
||||||
|
? StringFilter(
|
||||||
|
filterString: d.substring(1),
|
||||||
|
type: ComparisonType.endsWithIgnoreCase,
|
||||||
|
)
|
||||||
|
: StringFilter(
|
||||||
|
filterString: d,
|
||||||
|
type: ComparisonType.equalsIgnoreCase,
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
contentFilters: keywords
|
contentFilters: keywords
|
||||||
.map((k) => StringFilter(
|
.map((k) => StringFilter(
|
||||||
filterString: k, type: ComparisonType.containsCaseInsensitive))
|
filterString: k, type: ComparisonType.containsIgnoreCase))
|
||||||
.toList(),
|
.toList(),
|
||||||
hashtagFilters: hashtags
|
hashtagFilters: hashtags
|
||||||
.map((h) => StringFilter(
|
.map((h) => StringFilter(
|
||||||
|
@ -58,6 +72,7 @@ class TimelineEntryFilter {
|
||||||
'action': action.name,
|
'action': action.name,
|
||||||
'name': name,
|
'name': name,
|
||||||
'authorFilters': authorFilters.map((f) => f.toJson()),
|
'authorFilters': authorFilters.map((f) => f.toJson()),
|
||||||
|
'domainFilters': domainFilters.map((f) => f.toJson()),
|
||||||
'contentFilters': contentFilters.map((f) => f.toJson()),
|
'contentFilters': contentFilters.map((f) => f.toJson()),
|
||||||
'hashtagFilters': hashtagFilters.map((f) => f.toJson()),
|
'hashtagFilters': hashtagFilters.map((f) => f.toJson()),
|
||||||
};
|
};
|
||||||
|
@ -69,6 +84,9 @@ class TimelineEntryFilter {
|
||||||
authorFilters: (json['authorFilters'] as List<dynamic>)
|
authorFilters: (json['authorFilters'] as List<dynamic>)
|
||||||
.map((json) => StringFilter.fromJson(json))
|
.map((json) => StringFilter.fromJson(json))
|
||||||
.toList(),
|
.toList(),
|
||||||
|
domainFilters: (json['domainFilters'] as List<dynamic>)
|
||||||
|
.map((json) => StringFilter.fromJson(json))
|
||||||
|
.toList(),
|
||||||
contentFilters: (json['contentFilters'] as List<dynamic>)
|
contentFilters: (json['contentFilters'] as List<dynamic>)
|
||||||
.map((json) => StringFilter.fromJson(json))
|
.map((json) => StringFilter.fromJson(json))
|
||||||
.toList(),
|
.toList(),
|
||||||
|
|
|
@ -46,14 +46,16 @@ FilterResult runFilters(
|
||||||
extension StringFilterOps on StringFilter {
|
extension StringFilterOps on StringFilter {
|
||||||
bool isFiltered(String value) {
|
bool isFiltered(String value) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case ComparisonType.containsCaseSensitive:
|
case ComparisonType.contains:
|
||||||
return value.contains(filterString);
|
return value.contains(filterString);
|
||||||
case ComparisonType.containsCaseInsensitive:
|
case ComparisonType.containsIgnoreCase:
|
||||||
return value.toLowerCase().contains(filterString.toLowerCase());
|
return value.toLowerCase().contains(filterString.toLowerCase());
|
||||||
case ComparisonType.equals:
|
case ComparisonType.equals:
|
||||||
return value == filterString;
|
return value == filterString;
|
||||||
case ComparisonType.equalsIgnoreCase:
|
case ComparisonType.equalsIgnoreCase:
|
||||||
return value.toLowerCase() == filterString.toLowerCase();
|
return value.toLowerCase() == filterString.toLowerCase();
|
||||||
|
case ComparisonType.endsWithIgnoreCase:
|
||||||
|
return value.toLowerCase().endsWith(filterString.toLowerCase());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,6 +63,7 @@ extension StringFilterOps on StringFilter {
|
||||||
extension TimelineEntryFilterOps on TimelineEntryFilter {
|
extension TimelineEntryFilterOps on TimelineEntryFilter {
|
||||||
bool isFiltered(TimelineEntry entry) {
|
bool isFiltered(TimelineEntry entry) {
|
||||||
if (authorFilters.isEmpty &&
|
if (authorFilters.isEmpty &&
|
||||||
|
domainFilters.isEmpty &&
|
||||||
hashtagFilters.isEmpty &&
|
hashtagFilters.isEmpty &&
|
||||||
contentFilters.isEmpty) {
|
contentFilters.isEmpty) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -84,6 +87,16 @@ extension TimelineEntryFilterOps on TimelineEntryFilter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var domainFiltered = domainFilters.isEmpty ? true : false;
|
||||||
|
for (final filter in domainFilters) {
|
||||||
|
final domain =
|
||||||
|
Uri.tryParse(entry.externalLink)?.host ?? entry.externalLink;
|
||||||
|
if (filter.isFiltered(domain)) {
|
||||||
|
domainFiltered = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var contentFiltered = contentFilters.isEmpty ? true : false;
|
var contentFiltered = contentFilters.isEmpty ? true : false;
|
||||||
for (final filter in contentFilters) {
|
for (final filter in contentFilters) {
|
||||||
if (filter.isFiltered(entry.body)) {
|
if (filter.isFiltered(entry.body)) {
|
||||||
|
@ -92,6 +105,9 @@ extension TimelineEntryFilterOps on TimelineEntryFilter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return authorFiltered && hashtagFiltered && contentFiltered;
|
return authorFiltered &&
|
||||||
|
domainFiltered &&
|
||||||
|
hashtagFiltered &&
|
||||||
|
contentFiltered;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,13 +7,47 @@ import 'package:relatica/utils/filter_runner.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
final entries = [
|
final entries = [
|
||||||
TimelineEntry(body: 'Hello world', authorId: '1', tags: ['greeting']),
|
TimelineEntry(
|
||||||
TimelineEntry(body: 'Goodbye', authorId: '1', tags: ['SendOff']),
|
body: 'Hello world',
|
||||||
TimelineEntry(body: 'Lorem ipsum', authorId: '1', tags: ['latin']),
|
authorId: '1',
|
||||||
TimelineEntry(body: 'Hello world', authorId: '2', tags: ['greeting']),
|
tags: ['greeting'],
|
||||||
TimelineEntry(body: 'Goodbye', authorId: '2', tags: ['SendOff']),
|
externalLink: 'http://mastodon.social/@user1/1234',
|
||||||
TimelineEntry(body: 'Lorem ipsum', authorId: '2', tags: ['LATIN']),
|
),
|
||||||
TimelineEntry(body: 'Chao', authorId: '2', tags: ['sendoff']),
|
TimelineEntry(
|
||||||
|
body: 'Goodbye',
|
||||||
|
authorId: '1',
|
||||||
|
tags: ['SendOff'],
|
||||||
|
externalLink: 'http://mastodon.social/@user1/4567'),
|
||||||
|
TimelineEntry(
|
||||||
|
body: 'Lorem ipsum',
|
||||||
|
authorId: '1',
|
||||||
|
tags: ['latin'],
|
||||||
|
externalLink: 'http://mastodon.social/@user1/7890',
|
||||||
|
),
|
||||||
|
TimelineEntry(
|
||||||
|
body: 'Hello world',
|
||||||
|
authorId: '2',
|
||||||
|
tags: ['greeting'],
|
||||||
|
externalLink: 'http://trolltodon.social/@user2/12',
|
||||||
|
),
|
||||||
|
TimelineEntry(
|
||||||
|
body: 'Goodbye',
|
||||||
|
authorId: '2',
|
||||||
|
tags: ['SendOff'],
|
||||||
|
externalLink: 'http://trolltodon.social/@user2/34',
|
||||||
|
),
|
||||||
|
TimelineEntry(
|
||||||
|
body: 'Lorem ipsum',
|
||||||
|
authorId: '2',
|
||||||
|
tags: ['LATIN'],
|
||||||
|
externalLink: 'http://trolltodon.social/@user2/56',
|
||||||
|
),
|
||||||
|
TimelineEntry(
|
||||||
|
body: 'Chao',
|
||||||
|
authorId: '2',
|
||||||
|
tags: ['sendoff'],
|
||||||
|
externalLink: 'http://trolltodon.social/@user2/78',
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
group('Test StringFilter', () {
|
group('Test StringFilter', () {
|
||||||
|
@ -37,20 +71,30 @@ void main() {
|
||||||
expect(filter.isFiltered('hello!'), equals(false));
|
expect(filter.isFiltered('hello!'), equals(false));
|
||||||
expect(filter.isFiltered('help'), equals(false));
|
expect(filter.isFiltered('help'), equals(false));
|
||||||
});
|
});
|
||||||
test('Test containsCaseSensitive', () {
|
test('Test endsWithIgnoresCase', () {
|
||||||
|
const filter = StringFilter(
|
||||||
|
filterString: 'world',
|
||||||
|
type: ComparisonType.endsWithIgnoreCase,
|
||||||
|
);
|
||||||
|
expect(filter.isFiltered('world'), equals(true));
|
||||||
|
expect(filter.isFiltered('hello WORld'), equals(true));
|
||||||
|
expect(filter.isFiltered('worldwide'), equals(false));
|
||||||
|
expect(filter.isFiltered('hello world!'), equals(false));
|
||||||
|
});
|
||||||
|
test('Test contains', () {
|
||||||
const filter = StringFilter(
|
const filter = StringFilter(
|
||||||
filterString: 'hello',
|
filterString: 'hello',
|
||||||
type: ComparisonType.containsCaseSensitive,
|
type: ComparisonType.contains,
|
||||||
);
|
);
|
||||||
expect(filter.isFiltered('hello world'), equals(true));
|
expect(filter.isFiltered('hello world'), equals(true));
|
||||||
expect(filter.isFiltered('Hello World'), equals(false));
|
expect(filter.isFiltered('Hello World'), equals(false));
|
||||||
expect(filter.isFiltered('hello world'), equals(true));
|
expect(filter.isFiltered('hello world'), equals(true));
|
||||||
expect(filter.isFiltered('help'), equals(false));
|
expect(filter.isFiltered('help'), equals(false));
|
||||||
});
|
});
|
||||||
test('Test containsCaseInsensitive', () {
|
test('Test containsIgnoreCase', () {
|
||||||
const filter = StringFilter(
|
const filter = StringFilter(
|
||||||
filterString: 'hello',
|
filterString: 'hello',
|
||||||
type: ComparisonType.containsCaseInsensitive,
|
type: ComparisonType.containsIgnoreCase,
|
||||||
);
|
);
|
||||||
expect(filter.isFiltered('hello world'), equals(true));
|
expect(filter.isFiltered('hello world'), equals(true));
|
||||||
expect(filter.isFiltered('Hello World'), equals(true));
|
expect(filter.isFiltered('Hello World'), equals(true));
|
||||||
|
@ -91,6 +135,30 @@ void main() {
|
||||||
expect(actual, equals(expected));
|
expect(actual, equals(expected));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('Test Domain Filter', () {
|
||||||
|
test('Exact match', () {
|
||||||
|
final filter = TimelineEntryFilter.create(
|
||||||
|
action: TimelineEntryFilterAction.hide,
|
||||||
|
name: 'filter',
|
||||||
|
domains: ['trolltodon.social'],
|
||||||
|
);
|
||||||
|
final expected = [false, false, false, true, true, true, true];
|
||||||
|
final actual = entries.map((e) => filter.isFiltered(e)).toList();
|
||||||
|
expect(actual, equals(expected));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Start wildcard', () {
|
||||||
|
final filter = TimelineEntryFilter.create(
|
||||||
|
action: TimelineEntryFilterAction.hide,
|
||||||
|
name: 'filter',
|
||||||
|
domains: ['*odon.social'],
|
||||||
|
);
|
||||||
|
final expected = [true, true, true, true, true, true, true];
|
||||||
|
final actual = entries.map((e) => filter.isFiltered(e)).toList();
|
||||||
|
expect(actual, equals(expected));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('Test Tag Filter', () {
|
test('Test Tag Filter', () {
|
||||||
final filter = TimelineEntryFilter.create(
|
final filter = TimelineEntryFilter.create(
|
||||||
action: TimelineEntryFilterAction.hide,
|
action: TimelineEntryFilterAction.hide,
|
||||||
|
|
Ładowanie…
Reference in New Issue