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 11b242edbf
commit 0e7faaeb90
15 changed files with 560 additions and 15 deletions

View file

@ -0,0 +1,85 @@
import 'dart:typed_data';
import 'package:commons_core/commons_core.dart';
import 'package:nostr/nostr.dart';
import 'package:test/test.dart';
import '../support/mini_relay.dart';
/// Reports are standard NIP-56 events (kind 1984): a `p` tag naming the
/// reported key (with the report type), optionally an `e` tag naming the
/// offending event, and free text in the content. Standard on purpose any
/// Nostr relay or moderation tool understands them.
void main() {
late MiniRelay relay;
setUp(() async => relay = await MiniRelay.start());
tearDown(() async => relay.stop());
Future<NostrIdentity> idFor(int fill) => NostrKeyDerivation.deriveFromSeed(
Uint8List(32)..fillRange(0, 32, fill),
);
test('reporting a person publishes a kind-1984 event with the typed p tag',
() async {
final alice = await idFor(1);
final conn = await NostrConnection.connect(relay.url, identity: alice);
final reports = NostrReportTransport(conn);
await reports.report(
subjectPubkey: 'ab' * 32,
reason: ReportReason.spam,
details: 'floods the market with fake offers',
);
final stored = await conn
.reqOnce(Filter(kinds: const [NostrReportTransport.kindReport]));
expect(stored, hasLength(1));
final event = stored.single;
expect(event.pubkey, alice.publicKeyHex);
expect(event.content, 'floods the market with fake offers');
expect(event.tags, anyElement(equals(['p', 'ab' * 32, 'spam'])));
await reports.close();
});
test('reporting an offending event adds the typed e tag too', () async {
final alice = await idFor(1);
final conn = await NostrConnection.connect(relay.url, identity: alice);
final reports = NostrReportTransport(conn);
await reports.report(
subjectPubkey: 'cd' * 32,
eventId: 'ef' * 32,
reason: ReportReason.illegal,
);
final stored = await conn
.reqOnce(Filter(kinds: const [NostrReportTransport.kindReport]));
final event = stored.single;
expect(event.tags, anyElement(equals(['e', 'ef' * 32, 'illegal'])));
expect(event.tags, anyElement(equals(['p', 'cd' * 32, 'illegal'])));
expect(event.content, isEmpty);
await reports.close();
});
test('reporting addressable content (an offer) adds the typed a tag',
() async {
final alice = await idFor(1);
final conn = await NostrConnection.connect(relay.url, identity: alice);
final reports = NostrReportTransport(conn);
final address = '30402:${'cd' * 32}:tomate-1';
await reports.report(
subjectPubkey: 'cd' * 32,
eventAddress: address,
reason: ReportReason.spam,
);
final stored = await conn
.reqOnce(Filter(kinds: const [NostrReportTransport.kindReport]));
expect(
stored.single.tags,
anyElement(equals(['a', address, 'spam'])),
);
await reports.close();
});
}