feat(social): standard NIP-56 reporting — ReportTransport in commons_core, report sheet on offers and chats, local hide + optional block

This commit is contained in:
vjrj 2026-07-13 07:56:43 +02:00
parent b41dfd4248
commit 7869a7163b
15 changed files with 560 additions and 15 deletions

View file

@ -15,6 +15,7 @@ export 'src/social/certification.dart';
export 'src/social/geohash.dart';
export 'src/social/message_transport.dart';
export 'src/social/nostr_ids.dart';
export 'src/social/nostr/nip99_codec.dart';
export 'src/social/nostr/nostr_channel.dart';
export 'src/social/nostr/nostr_sync_transport.dart';
export 'src/social/nostr/nostr_connection.dart';
@ -22,6 +23,7 @@ export 'src/social/nostr/nostr_message_transport.dart';
export 'src/social/nostr/nostr_offer_transport.dart';
export 'src/social/nostr/nostr_profile_transport.dart';
export 'src/social/nostr/nostr_rating_transport.dart';
export 'src/social/nostr/nostr_report_transport.dart';
export 'src/social/nostr/nostr_trust_transport.dart';
export 'src/social/nostr/relay_pool.dart';
export 'src/social/offer.dart';
@ -29,6 +31,7 @@ export 'src/social/offer_transport.dart';
export 'src/social/profile_transport.dart';
export 'src/social/rating.dart';
export 'src/social/rating_transport.dart';
export 'src/social/report_transport.dart';
export 'src/social/sync_transport.dart';
export 'src/social/trust_transport.dart';
export 'src/social/web_of_trust.dart';

View file

@ -0,0 +1,43 @@
import 'package:nostr/nostr.dart';
import '../report_transport.dart';
import 'nostr_channel.dart';
/// Reports carried as standard NIP-56 events on the shared [NostrConnection]:
/// kind 1984, the reported key in a typed `p` tag (and the offending event in
/// a typed `e` tag when given), free text in the content. Standard on purpose
/// so any relay operator or moderation tool can act on them.
class NostrReportTransport implements ReportTransport {
NostrReportTransport(this._conn);
final NostrChannel _conn;
/// NIP-56 reporting kind.
static const kindReport = 1984;
@override
Future<void> report({
required String subjectPubkey,
String? eventId,
String? eventAddress,
required ReportReason reason,
String details = '',
}) async {
final type = reason.name;
final event = Event.from(
kind: kindReport,
content: details,
tags: [
if (eventId != null) ['e', eventId, type],
if (eventAddress != null) ['a', eventAddress, type],
['p', subjectPubkey, type],
],
secretKey: _conn.privateKeyHex,
);
final r = await _conn.publish(event);
if (!r.accepted) throw StateError('relay rejected report: ${r.message}');
}
@override
Future<void> close() => _conn.close();
}

View file

@ -0,0 +1,23 @@
/// The standard report categories (NIP-56). Kept as an enum so the app maps
/// its human wording onto interoperable values.
enum ReportReason { nudity, malware, profanity, illegal, spam, impersonation, other }
/// Flagging bad actors/content over the shared connection. Reports are public
/// signed statements ("this key/event breaks the rules") that relays and
/// moderation tools act on the network-side half of the report/block story
/// (the local half is each user's own blocklist, which lives in the app).
abstract interface class ReportTransport {
/// Publishes a report about [subjectPubkey]; when the complaint is about one
/// specific event pass its [eventId], or for addressable content such as
/// offers its address ([eventAddress], `kind:pubkey:d`). [details] is
/// optional free text for a human moderator.
Future<void> report({
required String subjectPubkey,
String? eventId,
String? eventAddress,
required ReportReason reason,
String details = '',
});
Future<void> close();
}