Šepot — end-to-end encrypted messenger

Initial public release. Anonymous on-device identity (no phone number,
e-mail, or password), Signal-protocol cryptography (X3DH + PQXDH, Double
Ratchet, sealed sender) via libsignal, a metadata-minimizing relay,
group chats, disappearing messages, encrypted local storage, optional
Tor transport, and a UI localized into Czech, English, German, Russian,
and Italian.

Monorepo: Flutter client (app/), Rust relay + wire types (rust/),
protobuf definitions (proto/), self-host deploy (deploy/).

Licensed under AGPL-3.0 with an app-store distribution exception (§7).
This commit is contained in:
Julius Joska
2026-07-11 17:06:39 +02:00
commit 8081d98c67
309 changed files with 44102 additions and 0 deletions
@@ -0,0 +1,50 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:sepot/models/conversation.dart';
void main() {
test('mute/archive přežijí JSON roundtrip', () {
final c = Conversation(
contactId: 'X',
muted: true,
mutedUntil: DateTime.parse('2026-07-09T10:00:00.000'),
archived: true,
);
final back =
Conversation.fromJson(jsonRoundtrip(c.toJson()));
expect(back.muted, isTrue);
expect(back.mutedUntil, DateTime.parse('2026-07-09T10:00:00.000'));
expect(back.archived, isTrue);
});
test('legacy JSON bez nových klíčů projde s defaulty', () {
final legacy = {
'contactId': 'Y',
'messages': <dynamic>[],
'lastActivity': '2026-07-08T10:00:00.000',
'disappearAfterSeconds': null,
};
final c = Conversation.fromJson(legacy);
expect(c.muted, isFalse);
expect(c.archived, isFalse);
expect(c.mutedUntil, isNull);
expect(c.isMuted, isFalse);
});
test('isMuted: dočasné ztlumení vyprší', () {
final past = Conversation(
contactId: 'Z',
muted: true,
mutedUntil: DateTime.now().subtract(const Duration(hours: 1)));
expect(past.isMuted, isFalse); // vypršelo
final future = Conversation(
contactId: 'Z',
muted: true,
mutedUntil: DateTime.now().add(const Duration(hours: 1)));
expect(future.isMuted, isTrue);
final forever = Conversation(contactId: 'Z', muted: true);
expect(forever.isMuted, isTrue);
});
}
Map<String, dynamic> jsonRoundtrip(Map<String, dynamic> m) =>
Map<String, dynamic>.from(m);