Files
Rijad Zuzo f655adfbea feat: add local-first private AI digest workflow
Migrate app code into canonical feature slices, add phone-only AI digest scheduling and review, wire local notification/background task support, and cover the flow with tests.
2026-05-17 00:17:20 +02:00

73 lines
1.8 KiB
Dart

class SignalSharePayload {
const SignalSharePayload({
required this.messageText,
this.sourceDisplayName,
this.sourceUserId,
this.sourceThreadId,
});
final String messageText;
final String? sourceDisplayName;
final String? sourceUserId;
final String? sourceThreadId;
}
/// Parses raw shared text from Signal into normalized sender/message fields.
class SignalShareParser {
const SignalShareParser._();
static final RegExp _namePrefixPattern = RegExp(
r'^([^:\n]{2,80}):\s*(.+)$',
dotAll: true,
);
static SignalSharePayload parse(String raw) {
final String input = raw.trim();
if (input.isEmpty) {
return const SignalSharePayload(messageText: '');
}
final RegExpMatch? match = _namePrefixPattern.firstMatch(input);
if (match == null) {
return SignalSharePayload(messageText: input);
}
final String? senderName = _cleanSender(match.group(1));
final String messageText = match.group(2)?.trim() ?? input;
return SignalSharePayload(
messageText: messageText,
sourceDisplayName: senderName,
sourceUserId: _normalizedKey(senderName),
sourceThreadId: null,
);
}
static bool looksLikeSignalMessage(String raw) {
final String input = raw.trim();
if (input.isEmpty) {
return false;
}
return _namePrefixPattern.hasMatch(input);
}
static String? _cleanSender(String? input) {
if (input == null) {
return null;
}
final String cleaned = input.trim();
return cleaned.isEmpty ? null : cleaned;
}
static String? _normalizedKey(String? input) {
if (input == null) {
return null;
}
final String key = input
.toLowerCase()
.replaceAll(RegExp(r'[^a-z0-9]+'), ' ')
.trim()
.replaceAll(RegExp(r'\s+'), ' ');
return key.isEmpty ? null : 'signal:$key';
}
}