mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-08-26 21:02:28 +02:00
fix(metadata): prompt verification during autofill #511
This commit is contained in:
@@ -128,6 +128,7 @@ func (m *extensionManager) SearchTracksWithMetadataProvidersForItemID(query stri
|
||||
|
||||
tracks := make([]ExtTrackMetadata, 0, limit)
|
||||
seenTracks := make(map[string]struct{})
|
||||
var verificationErr error
|
||||
for _, providerID := range orderedProviderIDs {
|
||||
if isDownloadCancelled(itemID) {
|
||||
return nil, ErrDownloadCancelled
|
||||
@@ -150,6 +151,13 @@ func (m *extensionManager) SearchTracksWithMetadataProvidersForItemID(query stri
|
||||
if errors.Is(err, ErrDownloadCancelled) {
|
||||
return nil, ErrDownloadCancelled
|
||||
}
|
||||
if verificationErr == nil && strings.EqualFold(classifyDownloadErrorType(err.Error()), "verification_required") {
|
||||
verificationErr = fmt.Errorf(
|
||||
"verification_required: extension '%s' needs verification: %w",
|
||||
providerID,
|
||||
err,
|
||||
)
|
||||
}
|
||||
GoLog("[MetadataSearch] Search error from %s: %v\n", providerID, err)
|
||||
continue
|
||||
}
|
||||
@@ -169,6 +177,9 @@ func (m *extensionManager) SearchTracksWithMetadataProvidersForItemID(query stri
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(tracks) == 0 && verificationErr != nil {
|
||||
return nil, verificationErr
|
||||
}
|
||||
|
||||
return tracks, nil
|
||||
}
|
||||
|
||||
@@ -991,6 +991,70 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> {
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> _searchAutoFillCandidates({
|
||||
required String query,
|
||||
required bool usesAutomaticProvider,
|
||||
required Extension? selectedProvider,
|
||||
required ExtensionState extensionState,
|
||||
bool allowVerificationRetry = true,
|
||||
}) async {
|
||||
try {
|
||||
if (usesAutomaticProvider) {
|
||||
return await PlatformBridge.searchTracksWithMetadataProviders(
|
||||
query,
|
||||
limit: 5,
|
||||
);
|
||||
}
|
||||
|
||||
final provider = selectedProvider!;
|
||||
if (provider.hasCustomSearch) {
|
||||
final trackFilter = provider.searchBehavior?.filterIdForKind('track');
|
||||
return await PlatformBridge.customSearchWithExtension(
|
||||
provider.id,
|
||||
query,
|
||||
options: {'limit': 5, 'filter': ?trackFilter},
|
||||
);
|
||||
}
|
||||
return await PlatformBridge.searchTracksWithMetadataProvider(
|
||||
provider.id,
|
||||
query,
|
||||
limit: 5,
|
||||
);
|
||||
} catch (error) {
|
||||
if (!allowVerificationRetry || !isExtensionVerificationRequired(error)) {
|
||||
rethrow;
|
||||
}
|
||||
|
||||
final extensionId = usesAutomaticProvider
|
||||
? extensionIdFromVerificationError(
|
||||
error,
|
||||
extensionState.extensions.map((extension) => extension.id),
|
||||
)
|
||||
: selectedProvider?.id;
|
||||
if (extensionId == null || extensionId.isEmpty) rethrow;
|
||||
|
||||
_log.i(
|
||||
'Metadata autofill requires verification; waiting for $extensionId',
|
||||
);
|
||||
final verified = await openVerificationAndAwaitGrant(
|
||||
extensionId,
|
||||
browserMode: ProviderScope.containerOf(
|
||||
context,
|
||||
listen: false,
|
||||
).read(settingsProvider).extensionVerificationBrowserMode,
|
||||
);
|
||||
if (!verified || !mounted) rethrow;
|
||||
|
||||
return _searchAutoFillCandidates(
|
||||
query: query,
|
||||
usesAutomaticProvider: usesAutomaticProvider,
|
||||
selectedProvider: selectedProvider,
|
||||
extensionState: extensionState,
|
||||
allowVerificationRetry: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _fetchAutoFillPreview() async {
|
||||
if (_autoFillFields.isEmpty) {
|
||||
_showSheetSnackBar(context.l10n.editMetadataAutoFillNoneSelected);
|
||||
@@ -1067,28 +1131,12 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> {
|
||||
|
||||
if (needsTrackLookup && best == null) {
|
||||
final query = queryParts.join(' ');
|
||||
final List<Map<String, dynamic>> results;
|
||||
if (usesAutomaticProvider) {
|
||||
results = await PlatformBridge.searchTracksWithMetadataProviders(
|
||||
query,
|
||||
limit: 5,
|
||||
);
|
||||
} else if (selectedProvider!.hasCustomSearch) {
|
||||
final trackFilter = selectedProvider.searchBehavior?.filterIdForKind(
|
||||
'track',
|
||||
);
|
||||
results = await PlatformBridge.customSearchWithExtension(
|
||||
selectedProvider.id,
|
||||
query,
|
||||
options: {'limit': 5, 'filter': ?trackFilter},
|
||||
);
|
||||
} else {
|
||||
results = await PlatformBridge.searchTracksWithMetadataProvider(
|
||||
selectedProvider.id,
|
||||
query,
|
||||
limit: 5,
|
||||
);
|
||||
}
|
||||
final results = await _searchAutoFillCandidates(
|
||||
query: query,
|
||||
usesAutomaticProvider: usesAutomaticProvider,
|
||||
selectedProvider: selectedProvider,
|
||||
extensionState: extensionState,
|
||||
);
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import 'package:spotiflac_android/utils/logger.dart';
|
||||
import 'package:spotiflac_android/utils/lyrics_metadata_helper.dart';
|
||||
import 'package:spotiflac_android/utils/mime_utils.dart';
|
||||
import 'package:spotiflac_android/utils/image_cache_utils.dart';
|
||||
import 'package:spotiflac_android/utils/extension_auth_launcher.dart';
|
||||
|
||||
import 'package:spotiflac_android/utils/string_utils.dart';
|
||||
import 'package:spotiflac_android/utils/user_facing_error.dart';
|
||||
|
||||
@@ -25,6 +25,37 @@ bool isExtensionVerificationRequired(Object error) {
|
||||
_containsHttpStatusCode(message, '428');
|
||||
}
|
||||
|
||||
String? extensionIdFromVerificationError(
|
||||
Object error,
|
||||
Iterable<String> knownExtensionIds,
|
||||
) {
|
||||
final idsByNormalized = <String, String>{
|
||||
for (final id in knownExtensionIds)
|
||||
if (id.trim().isNotEmpty) id.trim().toLowerCase(): id.trim(),
|
||||
};
|
||||
if (idsByNormalized.isEmpty) return null;
|
||||
|
||||
final message = error.toString();
|
||||
final canonicalMatch = RegExp(
|
||||
r'''extension\s+['"]([^'"]+)['"]''',
|
||||
caseSensitive: false,
|
||||
).firstMatch(message);
|
||||
final canonicalId = canonicalMatch?.group(1)?.trim().toLowerCase();
|
||||
if (canonicalId != null && idsByNormalized.containsKey(canonicalId)) {
|
||||
return idsByNormalized[canonicalId];
|
||||
}
|
||||
|
||||
final lowerMessage = message.toLowerCase();
|
||||
final orderedIds = idsByNormalized.keys.toList()
|
||||
..sort((a, b) => b.length.compareTo(a.length));
|
||||
for (final normalizedId in orderedIds) {
|
||||
if (lowerMessage.contains(normalizedId)) {
|
||||
return idsByNormalized[normalizedId];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
bool _containsHttpStatusCode(String message, String code) {
|
||||
return message.contains('http $code') ||
|
||||
message.contains('http status $code') ||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:spotiflac_android/utils/extension_auth_launcher.dart';
|
||||
|
||||
void main() {
|
||||
test('extracts the extension that raised a verification challenge', () {
|
||||
expect(
|
||||
extensionIdFromVerificationError(
|
||||
"verification_required: extension 'tidal-web' needs verification",
|
||||
const ['amazon-web', 'tidal-web'],
|
||||
),
|
||||
'tidal-web',
|
||||
);
|
||||
});
|
||||
|
||||
test('prefers the longest known extension id in legacy errors', () {
|
||||
expect(
|
||||
extensionIdFromVerificationError(
|
||||
'qobuz-web verification_required',
|
||||
const ['qobuz', 'qobuz-web'],
|
||||
),
|
||||
'qobuz-web',
|
||||
);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user