diff --git a/lib/app/changelog.dart b/lib/app/changelog.dart index 0e8ca39..b798e51 100644 --- a/lib/app/changelog.dart +++ b/lib/app/changelog.dart @@ -1,4 +1,13 @@ const lunarisChangelog = [ + LunarisChange( + version: '2.3.9', + title: 'Исправление входа и синхронизации аккаунтов Pawchive', + bullets: [ + 'Исправлены API эндпоинты Pawchive: пути запросов избранных авторов и управления подписками приведены в соответствие со спецификацией API v1 (/api/v1/account/favorites, /api/v1/favorites/creator/...).', + 'Устранена ошибка 404: авторизация по логину и паролю, а также по session cookie теперь успешно завершается и сразу синхронизирует всех авторов.', + 'Информативные ошибки: понятные сообщения на русском при неверном пароле или сбое сети вместо сырых исключений Dio.', + ], + ), LunarisChange( version: '2.3.8', title: 'ABI-Aware Auto-Update — правильный APK для вашего процессора', diff --git a/lib/backend/services/pawchive_sync_service.dart b/lib/backend/services/pawchive_sync_service.dart index a011cae..5d747c9 100644 --- a/lib/backend/services/pawchive_sync_service.dart +++ b/lib/backend/services/pawchive_sync_service.dart @@ -3,7 +3,6 @@ import 'package:dio/dio.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/http/dio_client.dart'; -import '../di/backend_providers.dart'; import '../models/favorite_artist_item.dart'; import '../models/pawchive_account.dart'; import 'settings_service.dart'; @@ -98,10 +97,25 @@ class PawchiveSyncService { } // Check if redirect points back to login with flash error - if (redirectLocation.contains('/account/login')) { + if (redirectLocation.contains('/account/login') || + redirectLocation.contains('/login')) { throw StateError('Неверный логин или пароль'); } + // Check for flash errors in session cookie if present + try { + final dotIdx = sessionValue.indexOf('.'); + final payloadBase64 = + dotIdx != -1 ? sessionValue.substring(0, dotIdx) : sessionValue; + final normalizedB64 = base64Url.normalize(payloadBase64); + final decoded = utf8.decode(base64Url.decode(normalizedB64)); + if (decoded.contains('is incorrect') || decoded.contains('_flashes')) { + throw StateError('Неверный логин или пароль'); + } + } catch (e) { + if (e is StateError) rethrow; + } + // Verify session and fetch initial artists final remoteArtists = await fetchRemoteFavoriteArtists( baseUrl: normalizedBase, @@ -118,6 +132,15 @@ class PawchiveSyncService { isActive: true, syncedArtistsCount: remoteArtists.length, ); + } on DioException catch (e) { + final code = e.response?.statusCode; + if (code == 401 || code == 403) { + throw StateError('Неверный логин или пароль'); + } else if (code != null) { + throw StateError('Ошибка сервера Pawchive (HTTP $code)'); + } else { + throw StateError('Ошибка сети: ${e.message ?? e.type.name}'); + } } catch (e) { if (e is StateError || e is ArgumentError) rethrow; throw StateError('Ошибка входа в Pawchive: $e'); @@ -156,7 +179,17 @@ class PawchiveSyncService { isActive: true, syncedArtistsCount: remoteArtists.length, ); + } on DioException catch (e) { + final code = e.response?.statusCode; + if (code == 401 || code == 403) { + throw StateError('Сессия недействительна или истекла'); + } else if (code != null) { + throw StateError('Ошибка сервера Pawchive (HTTP $code)'); + } else { + throw StateError('Ошибка сети: ${e.message ?? e.type.name}'); + } } catch (e) { + if (e is StateError || e is ArgumentError) rethrow; throw StateError('Сессия недействительна или истекла: $e'); } } @@ -172,7 +205,7 @@ class PawchiveSyncService { : baseUrl; final response = await _dio.get( - '$normalizedBase/account/favorites', + '$normalizedBase/api/v1/account/favorites', queryParameters: {'type': 'artist'}, options: Options( headers: { @@ -180,9 +213,17 @@ class PawchiveSyncService { 'Accept': 'application/json', 'User-Agent': 'Lunaris/2.0.1 Flutter local booru browser', }, + validateStatus: (status) => status != null && status < 500, ), ); + if (response.statusCode == 401 || response.statusCode == 403) { + throw StateError('Сессия истекла или недействительна'); + } + if (response.statusCode != 200) { + throw StateError('Ошибка сервера Pawchive (HTTP ${response.statusCode})'); + } + final data = response.data; if (data is! List) return const []; @@ -344,7 +385,7 @@ class PawchiveSyncService { ? account.baseUrl.substring(0, account.baseUrl.length - 1) : account.baseUrl; - final path = '$normalizedBase/favorites/creator/$service/$artistId'; + final path = '$normalizedBase/api/v1/favorites/creator/$service/$artistId'; final options = Options( headers: { 'Cookie': 'session=$cleanCookie', diff --git a/pubspec.yaml b/pubspec.yaml index 0b87121..8cb4c8b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: gel_rule_app description: Local backend/core layer for a Flutter booru browser app. publish_to: "none" -version: 2.3.8+48 +version: 2.3.9+49 environment: sdk: ">=3.3.0 <4.0.0" diff --git a/test/backend/pawchive_sync_test.dart b/test/backend/pawchive_sync_test.dart index 78501f8..f9086c5 100644 --- a/test/backend/pawchive_sync_test.dart +++ b/test/backend/pawchive_sync_test.dart @@ -253,7 +253,7 @@ void main() { ); expect(addResult, isTrue); expect(lastMethod, equals('POST')); - expect(lastPath, equals('https://pawchive.pw/favorites/creator/fanbox/12345')); + expect(lastPath, equals('https://pawchive.pw/api/v1/favorites/creator/fanbox/12345')); final removeResult = await service.toggleRemoteFavorite( account: account, @@ -263,7 +263,7 @@ void main() { ); expect(removeResult, isTrue); expect(lastMethod, equals('DELETE')); - expect(lastPath, equals('https://pawchive.pw/favorites/creator/fanbox/12345')); + expect(lastPath, equals('https://pawchive.pw/api/v1/favorites/creator/fanbox/12345')); }); }); }