kopia lustrzana https://gitlab.com/mysocialportal/relatica
Add domain blocking capability to low level filters
rodzic
ed1b800e82
commit
5eb7dcf7fe
lib
models/filters
utils
|
@ -1,6 +1,7 @@
|
|||
enum ComparisonType {
|
||||
containsCaseSensitive,
|
||||
containsCaseInsensitive,
|
||||
contains,
|
||||
containsIgnoreCase,
|
||||
endsWithIgnoreCase,
|
||||
equals,
|
||||
equalsIgnoreCase,
|
||||
;
|
||||
|
|
|
@ -18,6 +18,7 @@ class TimelineEntryFilter {
|
|||
final TimelineEntryFilterAction action;
|
||||
final String name;
|
||||
final List<StringFilter> authorFilters;
|
||||
final List<StringFilter> domainFilters;
|
||||
final List<StringFilter> contentFilters;
|
||||
final List<StringFilter> hashtagFilters;
|
||||
|
||||
|
@ -25,6 +26,7 @@ class TimelineEntryFilter {
|
|||
required this.action,
|
||||
required this.name,
|
||||
required this.authorFilters,
|
||||
required this.domainFilters,
|
||||
required this.contentFilters,
|
||||
required this.hashtagFilters,
|
||||
});
|
||||
|
@ -33,6 +35,7 @@ class TimelineEntryFilter {
|
|||
required TimelineEntryFilterAction action,
|
||||
required String name,
|
||||
List<Connection> authors = const [],
|
||||
List<String> domains = const [],
|
||||
List<String> keywords = const [],
|
||||
List<String> hashtags = const [],
|
||||
}) {
|
||||
|
@ -43,9 +46,20 @@ class TimelineEntryFilter {
|
|||
.map((a) =>
|
||||
StringFilter(filterString: a.id, type: ComparisonType.equals))
|
||||
.toList(),
|
||||
domainFilters: domains
|
||||
.map((d) => d.startsWith('*')
|
||||
? StringFilter(
|
||||
filterString: d.substring(1),
|
||||
type: ComparisonType.endsWithIgnoreCase,
|
||||
)
|
||||
: StringFilter(
|
||||
filterString: d,
|
||||
type: ComparisonType.equalsIgnoreCase,
|
||||
))
|
||||
.toList(),
|
||||
contentFilters: keywords
|
||||
.map((k) => StringFilter(
|
||||
filterString: k, type: ComparisonType.containsCaseInsensitive))
|
||||
filterString: k, type: ComparisonType.containsIgnoreCase))
|
||||
.toList(),
|
||||
hashtagFilters: hashtags
|
||||
.map((h) => StringFilter(
|
||||
|
@ -58,6 +72,7 @@ class TimelineEntryFilter {
|
|||
'action': action.name,
|
||||
'name': name,
|
||||
'authorFilters': authorFilters.map((f) => f.toJson()),
|
||||
'domainFilters': domainFilters.map((f) => f.toJson()),
|
||||
'contentFilters': contentFilters.map((f) => f.toJson()),
|
||||
'hashtagFilters': hashtagFilters.map((f) => f.toJson()),
|
||||
};
|
||||
|
@ -69,6 +84,9 @@ class TimelineEntryFilter {
|
|||
authorFilters: (json['authorFilters'] as List<dynamic>)
|
||||
.map((json) => StringFilter.fromJson(json))
|
||||
.toList(),
|
||||
domainFilters: (json['domainFilters'] as List<dynamic>)
|
||||
.map((json) => StringFilter.fromJson(json))
|
||||
.toList(),
|
||||
contentFilters: (json['contentFilters'] as List<dynamic>)
|
||||
.map((json) => StringFilter.fromJson(json))
|
||||
.toList(),
|
||||
|
|
|
@ -46,14 +46,16 @@ FilterResult runFilters(
|
|||
extension StringFilterOps on StringFilter {
|
||||
bool isFiltered(String value) {
|
||||
switch (type) {
|
||||
case ComparisonType.containsCaseSensitive:
|
||||
case ComparisonType.contains:
|
||||
return value.contains(filterString);
|
||||
case ComparisonType.containsCaseInsensitive:
|
||||
case ComparisonType.containsIgnoreCase:
|
||||
return value.toLowerCase().contains(filterString.toLowerCase());
|
||||
case ComparisonType.equals:
|
||||
return value == filterString;
|
||||
case ComparisonType.equalsIgnoreCase:
|
||||
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 {
|
||||
bool isFiltered(TimelineEntry entry) {
|
||||
if (authorFilters.isEmpty &&
|
||||
domainFilters.isEmpty &&
|
||||
hashtagFilters.isEmpty &&
|
||||
contentFilters.isEmpty) {
|
||||
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;
|
||||
for (final filter in contentFilters) {
|
||||
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() {
|
||||
final entries = [
|
||||
TimelineEntry(body: 'Hello world', authorId: '1', tags: ['greeting']),
|
||||
TimelineEntry(body: 'Goodbye', authorId: '1', tags: ['SendOff']),
|
||||
TimelineEntry(body: 'Lorem ipsum', authorId: '1', tags: ['latin']),
|
||||
TimelineEntry(body: 'Hello world', authorId: '2', tags: ['greeting']),
|
||||
TimelineEntry(body: 'Goodbye', authorId: '2', tags: ['SendOff']),
|
||||
TimelineEntry(body: 'Lorem ipsum', authorId: '2', tags: ['LATIN']),
|
||||
TimelineEntry(body: 'Chao', authorId: '2', tags: ['sendoff']),
|
||||
TimelineEntry(
|
||||
body: 'Hello world',
|
||||
authorId: '1',
|
||||
tags: ['greeting'],
|
||||
externalLink: 'http://mastodon.social/@user1/1234',
|
||||
),
|
||||
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', () {
|
||||
|
@ -37,20 +71,30 @@ void main() {
|
|||
expect(filter.isFiltered('hello!'), 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(
|
||||
filterString: 'hello',
|
||||
type: ComparisonType.containsCaseSensitive,
|
||||
type: ComparisonType.contains,
|
||||
);
|
||||
expect(filter.isFiltered('hello world'), equals(true));
|
||||
expect(filter.isFiltered('Hello World'), equals(false));
|
||||
expect(filter.isFiltered('hello world'), equals(true));
|
||||
expect(filter.isFiltered('help'), equals(false));
|
||||
});
|
||||
test('Test containsCaseInsensitive', () {
|
||||
test('Test containsIgnoreCase', () {
|
||||
const filter = StringFilter(
|
||||
filterString: 'hello',
|
||||
type: ComparisonType.containsCaseInsensitive,
|
||||
type: ComparisonType.containsIgnoreCase,
|
||||
);
|
||||
expect(filter.isFiltered('hello world'), equals(true));
|
||||
expect(filter.isFiltered('Hello World'), equals(true));
|
||||
|
@ -91,6 +135,30 @@ void main() {
|
|||
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', () {
|
||||
final filter = TimelineEntryFilter.create(
|
||||
action: TimelineEntryFilterAction.hide,
|
||||
|
|
Ładowanie…
Reference in New Issue