feat(social): local blocklist — hide blocked authors' offers, chats and incoming messages, with unblock management
This commit is contained in:
parent
5d25f65f76
commit
11b242edbf
13 changed files with 399 additions and 4 deletions
|
|
@ -9,6 +9,7 @@ import 'notification_service.dart';
|
|||
import 'profile_cache.dart';
|
||||
import 'social_connection.dart';
|
||||
import 'social_service.dart' show SocialSession;
|
||||
import 'social_settings.dart';
|
||||
import 'unread_service.dart';
|
||||
|
||||
/// App-wide inbox listener for private messages (NIP-17).
|
||||
|
|
@ -31,12 +32,14 @@ class InboxService {
|
|||
ProfileCache? profileCache,
|
||||
UnreadService? unread,
|
||||
NotificationService? notifications,
|
||||
SocialSettings? settings,
|
||||
}) : _connection = connection,
|
||||
_selfPubkey = selfPubkey,
|
||||
_store = store,
|
||||
_profileCache = profileCache,
|
||||
_unread = unread,
|
||||
_notifications = notifications;
|
||||
_notifications = notifications,
|
||||
_settings = settings;
|
||||
|
||||
final SocialConnection _connection;
|
||||
final String _selfPubkey;
|
||||
|
|
@ -45,6 +48,10 @@ class InboxService {
|
|||
final UnreadService? _unread;
|
||||
final NotificationService? _notifications;
|
||||
|
||||
/// Holds the local blocklist; messages from blocked peers are dropped before
|
||||
/// they are stored or notified. Null in tests → nothing filtered.
|
||||
final SocialSettings? _settings;
|
||||
|
||||
final _changes = StreamController<void>.broadcast();
|
||||
StreamSubscription<SocialSession?>? _sessionsSub;
|
||||
StreamSubscription<PrivateMessage>? _inboxSub;
|
||||
|
|
@ -86,6 +93,10 @@ class InboxService {
|
|||
/// testable seam (no relay).
|
||||
@visibleForTesting
|
||||
Future<void> ingest(PrivateMessage message) async {
|
||||
final settings = _settings;
|
||||
if (settings != null && await settings.isBlocked(message.fromPubkey)) {
|
||||
return; // blocked peer — dropped before storage, unread or notification
|
||||
}
|
||||
final stored = await _store.append(message.fromPubkey, message);
|
||||
if (!stored) return; // a re-delivered duplicate
|
||||
if (!_changes.isClosed) _changes.add(null);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ class SocialSettings {
|
|||
static const _areaKey = 'tane.social.area_geohash';
|
||||
static const _relaysKey = 'tane.social.relays';
|
||||
static const _searchPrecisionKey = 'tane.social.search_precision';
|
||||
static const _blockedKey = 'tane.social.blocked_pubkeys';
|
||||
|
||||
/// How wide "your zone" searches, as a geohash prefix length: 5 ≈ ±2.4 km
|
||||
/// ("very close"), 4 ≈ ±20 km ("around here"), 3 ≈ ±78 km ("my region").
|
||||
|
|
@ -80,4 +81,32 @@ class SocialSettings {
|
|||
/// Whether the social layer has enough config to attempt going online.
|
||||
Future<bool> get isConfigured async =>
|
||||
(await relayUrls()).isNotEmpty && (await areaGeohash()).isNotEmpty;
|
||||
|
||||
/// Pubkeys (hex) this user has blocked: their offers are hidden, their
|
||||
/// chats disappear and their incoming messages are dropped. Purely local —
|
||||
/// nothing is published about who you block.
|
||||
Future<Set<String>> blockedPubkeys() async {
|
||||
final raw = await _store.read(_blockedKey);
|
||||
if (raw == null) return <String>{};
|
||||
return raw
|
||||
.split('\n')
|
||||
.map((s) => s.trim().toLowerCase())
|
||||
.where((s) => s.isNotEmpty)
|
||||
.toSet();
|
||||
}
|
||||
|
||||
Future<bool> isBlocked(String pubkeyHex) async =>
|
||||
(await blockedPubkeys()).contains(pubkeyHex.trim().toLowerCase());
|
||||
|
||||
Future<void> block(String pubkeyHex) async {
|
||||
final set = await blockedPubkeys()
|
||||
..add(pubkeyHex.trim().toLowerCase());
|
||||
await _store.write(_blockedKey, set.join('\n'));
|
||||
}
|
||||
|
||||
Future<void> unblock(String pubkeyHex) async {
|
||||
final set = await blockedPubkeys()
|
||||
..remove(pubkeyHex.trim().toLowerCase());
|
||||
await _store.write(_blockedKey, set.join('\n'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue