From da66dd44c911266f741ecfb959ea4157e8847e33 Mon Sep 17 00:00:00 2001 From: Owais Jamil Date: Tue, 19 May 2026 21:26:29 -0500 Subject: [PATCH] feat: update moderation service for public browsing --- .../moderation/data/moderation_service.dart | 38 +++++++++++++++++-- .../data/public_repository_factory.dart | 21 ++++++++-- .../data/moderation_service_test.dart | 35 ++++++++++++++++- 3 files changed, 86 insertions(+), 8 deletions(-) diff --git a/lib/features/moderation/data/moderation_service.dart b/lib/features/moderation/data/moderation_service.dart index ffa41d0..eaadb96 100644 --- a/lib/features/moderation/data/moderation_service.dart +++ b/lib/features/moderation/data/moderation_service.dart @@ -29,6 +29,27 @@ class ModerationService { _database = database, _accountDid = accountDid, _userDid = userDid, + _publicReadOnly = false, + _appViewContext = AppViewRequestContext( + appViewProvider: appViewProvider, + appViewProviderResolver: appViewProviderResolver, + ) { + _headers = _appViewContext.appBskyHeadersForEndpoint( + 'app.bsky.labeler.getServices', + _buildLabelerHeaders(const []), + ); + } + + ModerationService.public({ + required Bluesky bluesky, + AppDatabase? database, + String? appViewProvider, + String Function()? appViewProviderResolver, + }) : _bluesky = bluesky, + _database = database, + _accountDid = null, + _userDid = null, + _publicReadOnly = true, _appViewContext = AppViewRequestContext( appViewProvider: appViewProvider, appViewProviderResolver: appViewProviderResolver, @@ -43,6 +64,7 @@ class ModerationService { final AppDatabase? _database; final String? _accountDid; final String? _userDid; + final bool _publicReadOnly; final AppViewRequestContext _appViewContext; moderation.ModerationOpts? _opts; @@ -197,8 +219,8 @@ class ModerationService { return moderation.ModerationDecision( me: post.author.did == _resolvedUserDid, causes: [ - if (opts.prefs.hiddenPosts.contains(post.uri.toString())) _hiddenCause(), - ..._viewerCauses(post.author.viewer), + if (_hasAuthenticatedViewer(opts) && opts.prefs.hiddenPosts.contains(post.uri.toString())) _hiddenCause(), + ..._viewerCauses(post.author.viewer, opts), ..._labelCauses(post.labels ?? const [], moderation.LabelTarget.content, opts), ..._labelCauses(post.author.labels ?? const [], moderation.LabelTarget.account, opts), ], @@ -261,14 +283,18 @@ class ModerationService { return moderation.ModerationDecision( me: did == _resolvedUserDid, causes: [ - ..._viewerCauses(viewer), + ..._viewerCauses(viewer, opts), ..._labelCauses(labels, moderation.LabelTarget.account, opts), ..._labelCauses(labels, moderation.LabelTarget.profile, opts), ], ); } - List _viewerCauses(dynamic viewer) { + List _viewerCauses(dynamic viewer, moderation.ModerationOpts opts) { + if (!_hasAuthenticatedViewer(opts)) { + return const []; + } + if (viewer == null) { return const []; } @@ -467,6 +493,10 @@ class ModerationService { return _preferences; } + if (_publicReadOnly) { + return const []; + } + final headers = _appViewContext.appBskyHeadersWithoutProxy(); try { final prefsResponse = await _bluesky.actor.getPreferences($headers: headers); diff --git a/lib/features/public/data/public_repository_factory.dart b/lib/features/public/data/public_repository_factory.dart index 6d13ba8..61f9419 100644 --- a/lib/features/public/data/public_repository_factory.dart +++ b/lib/features/public/data/public_repository_factory.dart @@ -4,6 +4,7 @@ import 'package:lazurite/core/network/poptart_client_adapter.dart'; import 'package:lazurite/core/network/xrpc_network_interceptor.dart'; import 'package:lazurite/features/feed/data/feed_repository.dart'; import 'package:lazurite/features/feed/data/post_thread_repository.dart'; +import 'package:lazurite/features/moderation/data/moderation_service.dart'; import 'package:lazurite/features/profile/data/profile_repository.dart'; import 'package:lazurite/features/public/data/public_content_repository.dart'; import 'package:lazurite/features/search/data/search_repository.dart'; @@ -23,10 +24,12 @@ class PublicRepositoryFactory { } FeedRepository feedRepository(String providerKey) { + final client = bluesky(providerKey); return FeedRepository( - bluesky: bluesky(providerKey), + bluesky: client, database: database, accountDid: _publicAccountDid(providerKey), + moderationService: _publicModerationService(client, providerKey), appViewProvider: providerKey, ); } @@ -36,14 +39,22 @@ class PublicRepositoryFactory { } ProfileRepository profileRepository(String providerKey) { - return ProfileRepository(database: database, bluesky: bluesky(providerKey), appViewProvider: providerKey); + final client = bluesky(providerKey); + return ProfileRepository( + database: database, + bluesky: client, + moderationService: _publicModerationService(client, providerKey), + appViewProvider: providerKey, + ); } PostThreadRepository postThreadRepository(String providerKey) { + final client = bluesky(providerKey); return PostThreadRepository( - bluesky: bluesky(providerKey), + bluesky: client, database: database, accountDid: _publicAccountDid(providerKey), + moderationService: _publicModerationService(client, providerKey), appViewProvider: providerKey, ); } @@ -57,4 +68,8 @@ class PublicRepositoryFactory { } String _publicAccountDid(String providerKey) => 'public:${AppViewProviders.normalizeSettingKey(providerKey)}'; + + ModerationService _publicModerationService(Bluesky client, String providerKey) { + return ModerationService.public(bluesky: client, database: database, appViewProvider: providerKey); + } } diff --git a/test/features/moderation/data/moderation_service_test.dart b/test/features/moderation/data/moderation_service_test.dart index 8b9a7dd..628fdd5 100644 --- a/test/features/moderation/data/moderation_service_test.dart +++ b/test/features/moderation/data/moderation_service_test.dart @@ -4,7 +4,7 @@ import 'package:poptart_lex/com/atproto/label/defs.dart'; import 'package:bluesky_poptart/app/bsky/actor/defs.dart'; import 'package:bluesky_poptart/app/bsky/actor/get_preferences.dart'; import 'package:bluesky_poptart/app/bsky/actor/put_preferences.dart'; -import 'package:bluesky_poptart/app/bsky/feed/defs.dart'; +import 'package:bluesky_poptart/app/bsky/feed/defs.dart' hide ViewerState; import 'package:bluesky_poptart/app/bsky/labeler/defs.dart'; import 'package:bluesky_poptart/app/bsky/labeler/get_services.dart'; import 'package:drift/native.dart'; @@ -146,6 +146,39 @@ void main() { service.dispose(); }); + test('public mode skips account preferences and account viewer moderation', () async { + final actor = _FakeActorService( + preferences: [ + const UPreferences.labelersPref( + data: LabelersPref(labelers: [LabelerPrefItem(did: _customLabelerDid)]), + ), + ], + ); + final service = ModerationService.public( + bluesky: _testBlueskyClient(actor: actor, labeler: const _FakeLabelerService(), anonymous: true), + database: database, + appViewProvider: 'blacksky', + ); + + await service.ensureInitialized(); + + const blockedByProfile = ProfileViewDetailed( + did: 'did:plc:viewer-state', + handle: 'viewer-state.bsky.social', + viewer: ViewerState(blockedBy: true, muted: true), + ); + final profileUi = service.profileDetailedUi(blockedByProfile, moderation.ModerationBehaviorContext.profileView); + + expect(actor.getPreferencesCallCount, 0); + expect(service.currentOpts?.userDid, isNull); + expect(service.currentPreferences, isEmpty); + expect(service.currentHeaders['atproto-accept-labelers'], isNot(contains(_customLabelerDid))); + expect(profileUi.filter, isFalse); + expect(profileUi.blur, isFalse); + + service.dispose(); + }); + test('falls back to cached preferences after a request failure', () async { final seededService = ModerationService( bluesky: _testBlueskyClient( -- 2.51.2