Add viewed history, diagnostics, tag chips and SWF viewer
This commit is contained in:
@@ -43,7 +43,7 @@ class DanbooruProvider implements ContentProvider, CommentProvider {
|
||||
final response = await _dio.get<dynamic>(
|
||||
'/posts.json',
|
||||
queryParameters: {
|
||||
'page': page <= 0 ? 1 : page,
|
||||
'page': page + 1,
|
||||
'limit': limit,
|
||||
'tags': queryTags.join(' '),
|
||||
..._queryParameters,
|
||||
@@ -107,8 +107,7 @@ class DanbooruProvider implements ContentProvider, CommentProvider {
|
||||
..._queryParameters,
|
||||
},
|
||||
);
|
||||
final data = response.data;
|
||||
final items = data is List ? data : const [];
|
||||
final items = _commentItems(response.data);
|
||||
return items
|
||||
.whereType<Map>()
|
||||
.map((item) {
|
||||
@@ -117,9 +116,13 @@ class DanbooruProvider implements ContentProvider, CommentProvider {
|
||||
id: (json['id'] ?? '').toString(),
|
||||
postId: (json['post_id'] ?? postId).toString(),
|
||||
providerId: id,
|
||||
authorName: (json['creator_name'] ?? json['creator_id'] ?? 'user')
|
||||
authorName: (json['creator_name'] ??
|
||||
json['creator_id'] ??
|
||||
json['updater_name'] ??
|
||||
'user')
|
||||
.toString(),
|
||||
body: (json['body'] ?? json['text'] ?? json['comment'] ?? '')
|
||||
.toString(),
|
||||
body: (json['body'] ?? '').toString(),
|
||||
createdAt:
|
||||
DateTime.tryParse((json['created_at'] ?? '').toString()) ??
|
||||
DateTime.fromMillisecondsSinceEpoch(0),
|
||||
@@ -129,6 +132,20 @@ class DanbooruProvider implements ContentProvider, CommentProvider {
|
||||
.toList();
|
||||
}
|
||||
|
||||
List<dynamic> _commentItems(dynamic data) {
|
||||
if (data is List) return data;
|
||||
if (data is! Map) return const [];
|
||||
final json = Map<String, dynamic>.from(data);
|
||||
final comments = json['comments'];
|
||||
if (comments is List) return comments;
|
||||
final records = json['records'];
|
||||
if (records is List) return records;
|
||||
final comment = json['comment'];
|
||||
if (comment is List) return comment;
|
||||
if (comment is Map) return [comment];
|
||||
return const [];
|
||||
}
|
||||
|
||||
List<String> _topTags(TopPeriodFilter period) {
|
||||
return switch (period) {
|
||||
TopPeriodFilter.none => const [],
|
||||
|
||||
@@ -3,12 +3,14 @@ import 'package:dio/dio.dart';
|
||||
import '../../core/http/dio_client.dart';
|
||||
import '../mappers/e621_mapper.dart';
|
||||
import '../models/post.dart';
|
||||
import '../models/post_comment.dart';
|
||||
import '../models/provider_health.dart';
|
||||
import '../models/tag_suggestion.dart';
|
||||
import '../models/top_period_filter.dart';
|
||||
import 'content_provider.dart';
|
||||
|
||||
class E621Provider implements ContentProvider, TagSuggestionProvider {
|
||||
class E621Provider
|
||||
implements ContentProvider, TagSuggestionProvider, CommentProvider {
|
||||
E621Provider({
|
||||
required this.id,
|
||||
required this.name,
|
||||
@@ -38,7 +40,7 @@ class E621Provider implements ContentProvider, TagSuggestionProvider {
|
||||
final response = await _dio.get<dynamic>(
|
||||
'/posts.json',
|
||||
queryParameters: {
|
||||
'page': page <= 0 ? 1 : page,
|
||||
'page': page + 1,
|
||||
'limit': limit.clamp(1, 75),
|
||||
'tags': [
|
||||
...tags,
|
||||
@@ -125,6 +127,37 @@ class E621Provider implements ContentProvider, TagSuggestionProvider {
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<PostComment>> getComments(String postId) async {
|
||||
final response = await _dio.get<dynamic>(
|
||||
'/comments.json',
|
||||
queryParameters: {
|
||||
'search[post_id]': postId,
|
||||
'limit': 50,
|
||||
..._queryParameters,
|
||||
},
|
||||
);
|
||||
final items = response.data is List ? response.data as List : const [];
|
||||
return items
|
||||
.whereType<Map>()
|
||||
.map((item) {
|
||||
final json = Map<String, dynamic>.from(item);
|
||||
return PostComment(
|
||||
id: (json['id'] ?? '').toString(),
|
||||
postId: (json['post_id'] ?? postId).toString(),
|
||||
providerId: id,
|
||||
authorName: (json['creator_name'] ?? json['creator_id'] ?? 'user')
|
||||
.toString(),
|
||||
body: (json['body'] ?? '').toString(),
|
||||
createdAt:
|
||||
DateTime.tryParse((json['created_at'] ?? '').toString()) ??
|
||||
DateTime.fromMillisecondsSinceEpoch(0),
|
||||
);
|
||||
})
|
||||
.where((comment) => comment.body.trim().isNotEmpty)
|
||||
.toList();
|
||||
}
|
||||
|
||||
String _rating(String value) {
|
||||
return switch (value.toLowerCase()) {
|
||||
'safe' => 's',
|
||||
|
||||
@@ -148,13 +148,7 @@ class GelbooruProvider implements ContentProvider, CommentProvider {
|
||||
}
|
||||
|
||||
List<PostComment> _commentsFromResponse(dynamic data, String postId) {
|
||||
final raw = switch (data) {
|
||||
List() => data,
|
||||
{'comments': final List comments} => comments,
|
||||
{'@attributes': _, 'comment': final List comments} => comments,
|
||||
{'comment': final List comments} => comments,
|
||||
_ => const [],
|
||||
};
|
||||
final raw = _commentItems(data);
|
||||
return raw
|
||||
.whereType<Map>()
|
||||
.map((item) {
|
||||
@@ -165,10 +159,18 @@ class GelbooruProvider implements ContentProvider, CommentProvider {
|
||||
providerId: id,
|
||||
authorName: (json['creator'] ??
|
||||
json['creator_name'] ??
|
||||
json['creator_id'] ??
|
||||
json['user'] ??
|
||||
json['username'] ??
|
||||
json['author'] ??
|
||||
'')
|
||||
.toString(),
|
||||
body: (json['body'] ?? json['comment'] ?? '').toString(),
|
||||
body: (json['body'] ??
|
||||
json['comment'] ??
|
||||
json['content'] ??
|
||||
json['text'] ??
|
||||
'')
|
||||
.toString(),
|
||||
createdAt: DateTime.tryParse(
|
||||
(json['created_at'] ?? json['created'] ?? '').toString(),
|
||||
) ??
|
||||
@@ -178,4 +180,66 @@ class GelbooruProvider implements ContentProvider, CommentProvider {
|
||||
.where((comment) => comment.body.trim().isNotEmpty)
|
||||
.toList();
|
||||
}
|
||||
|
||||
List<dynamic> _commentItems(dynamic data) {
|
||||
if (data is List) return data;
|
||||
if (data is String) return _commentItemsFromText(data);
|
||||
if (data is! Map) return const [];
|
||||
final json = Map<String, dynamic>.from(data);
|
||||
final comments = json['comments'];
|
||||
if (comments is List) return comments;
|
||||
if (comments is Map) return _commentItems(comments);
|
||||
final comment = json['comment'];
|
||||
if (comment is List) return comment;
|
||||
if (comment is Map) return [comment];
|
||||
return const [];
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> _commentItemsFromText(String text) {
|
||||
final items = <Map<String, dynamic>>[];
|
||||
for (final match in RegExp(r'<comment\b([^>]*)>(.*?)</comment>',
|
||||
caseSensitive: false, dotAll: true)
|
||||
.allMatches(text)) {
|
||||
final attrs = _attributes(match.group(1) ?? '');
|
||||
final body = _tagValue(match.group(2) ?? '', 'body') ??
|
||||
_tagValue(match.group(2) ?? '', 'comment') ??
|
||||
match.group(2) ??
|
||||
'';
|
||||
items.add({...attrs, 'body': body});
|
||||
}
|
||||
for (final match
|
||||
in RegExp(r'<comment\b([^>]*)/?>', caseSensitive: false, dotAll: true)
|
||||
.allMatches(text)) {
|
||||
final attrs = _attributes(match.group(1) ?? '');
|
||||
if (attrs.isNotEmpty) items.add(attrs);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
Map<String, dynamic> _attributes(String source) {
|
||||
final result = <String, dynamic>{};
|
||||
for (final match in RegExp(r'''([A-Za-z0-9_:-]+)\s*=\s*["']([^"']*)["']''')
|
||||
.allMatches(source)) {
|
||||
result[match.group(1)!] = _decodeEntities(match.group(2) ?? '');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
String? _tagValue(String source, String tag) {
|
||||
final match =
|
||||
RegExp('<$tag[^>]*>(.*?)</$tag>', caseSensitive: false, dotAll: true)
|
||||
.firstMatch(source);
|
||||
final value = match?.group(1);
|
||||
return value == null ? null : _decodeEntities(value.trim());
|
||||
}
|
||||
|
||||
String _decodeEntities(String value) {
|
||||
return value
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll(''', "'")
|
||||
.replaceAll(''', "'");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@ import 'package:dio/dio.dart';
|
||||
import '../../core/http/dio_client.dart';
|
||||
import '../mappers/moebooru_mapper.dart';
|
||||
import '../models/post.dart';
|
||||
import '../models/post_comment.dart';
|
||||
import '../models/provider_health.dart';
|
||||
import '../models/tag_suggestion.dart';
|
||||
import '../models/top_period_filter.dart';
|
||||
import 'content_provider.dart';
|
||||
|
||||
class MoebooruProvider implements ContentProvider, TagSuggestionProvider {
|
||||
class MoebooruProvider
|
||||
implements ContentProvider, TagSuggestionProvider, CommentProvider {
|
||||
MoebooruProvider({
|
||||
required this.id,
|
||||
required this.name,
|
||||
@@ -38,7 +40,7 @@ class MoebooruProvider implements ContentProvider, TagSuggestionProvider {
|
||||
final response = await _dio.get<dynamic>(
|
||||
'/post.json',
|
||||
queryParameters: {
|
||||
'page': page <= 0 ? 1 : page,
|
||||
'page': page + 1,
|
||||
'limit': limit.clamp(1, 100),
|
||||
'tags': [
|
||||
...tags,
|
||||
@@ -127,6 +129,40 @@ class MoebooruProvider implements ContentProvider, TagSuggestionProvider {
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<PostComment>> getComments(String postId) async {
|
||||
final response = await _dio.get<dynamic>(
|
||||
'/comment.json',
|
||||
queryParameters: {
|
||||
'post_id': postId,
|
||||
'limit': 50,
|
||||
..._queryParameters,
|
||||
},
|
||||
);
|
||||
final items = response.data is List ? response.data as List : const [];
|
||||
return items
|
||||
.whereType<Map>()
|
||||
.map((item) {
|
||||
final json = Map<String, dynamic>.from(item);
|
||||
return PostComment(
|
||||
id: (json['id'] ?? '').toString(),
|
||||
postId: (json['post_id'] ?? postId).toString(),
|
||||
providerId: id,
|
||||
authorName:
|
||||
(json['creator'] ?? json['author'] ?? json['user'] ?? 'user')
|
||||
.toString(),
|
||||
body: (json['body'] ?? json['comment'] ?? json['text'] ?? '')
|
||||
.toString(),
|
||||
createdAt: DateTime.tryParse(
|
||||
(json['created_at'] ?? json['created_on'] ?? '').toString(),
|
||||
) ??
|
||||
DateTime.fromMillisecondsSinceEpoch(0),
|
||||
);
|
||||
})
|
||||
.where((comment) => comment.body.trim().isNotEmpty)
|
||||
.toList();
|
||||
}
|
||||
|
||||
List<String> _topTags(TopPeriodFilter period) {
|
||||
return switch (period) {
|
||||
TopPeriodFilter.none => const [],
|
||||
|
||||
@@ -5,6 +5,7 @@ import '../../core/utils/result.dart';
|
||||
import '../models/content_provider_config.dart';
|
||||
import '../models/post.dart';
|
||||
import '../models/post_comment.dart';
|
||||
import '../models/provider_diagnostics.dart';
|
||||
import '../models/provider_health.dart';
|
||||
import '../models/tag_suggestion.dart';
|
||||
import '../models/top_period_filter.dart';
|
||||
@@ -97,6 +98,7 @@ class ProviderManager {
|
||||
}
|
||||
|
||||
final posts = <Post>[];
|
||||
final seen = <String>{};
|
||||
for (final provider in providers) {
|
||||
try {
|
||||
final providerPosts = await provider.searchPosts(
|
||||
@@ -106,8 +108,25 @@ class ProviderManager {
|
||||
rating: rating,
|
||||
topPeriod: topPeriod,
|
||||
);
|
||||
posts.addAll(providerPosts);
|
||||
for (final post in providerPosts) {
|
||||
if (seen.add(post.cacheKey)) posts.add(post);
|
||||
}
|
||||
await _repository.saveDiagnostics(
|
||||
ProviderDiagnostics(
|
||||
providerId: provider.id,
|
||||
lastSearchAt: DateTime.now(),
|
||||
lastResultCount: providerPosts.length,
|
||||
),
|
||||
);
|
||||
} catch (_) {
|
||||
await _repository.saveDiagnostics(
|
||||
ProviderDiagnostics(
|
||||
providerId: provider.id,
|
||||
lastSearchAt: DateTime.now(),
|
||||
lastResultCount: 0,
|
||||
lastErrorMessage: 'Search failed',
|
||||
),
|
||||
);
|
||||
await _repository.saveHealth(
|
||||
ProviderHealth(
|
||||
providerId: provider.id,
|
||||
|
||||
Reference in New Issue
Block a user