/// Parsed parts of a Talk push subject text. /// /// Format verified against nextcloud/spreed `Notifier::parseChatMessage`: /// with message preview the parsed subject is `"{user}\n{message}"` for 1:1 /// chats and `'{user} in {call}' . "\n{message}"` for groups — header and /// message are separated by a NEWLINE, and the notifications app forwards the /// parsed subject unmodified (only shortened). The German translation of /// `{user} in {call}` is identical (`l10n/de.json`), so the ` in ` separator /// holds for de_DE. Legacy/other variants using `": "` are kept as fallback; /// splitting the header at the LAST ` in ` is robust against sender names /// containing "in" (only a display name literally containing ` in ` in a 1:1 /// chat would still mis-split — accepted heuristic limit). ({String sender, String? roomName, String text}) parseTalkSubject(String raw) { String header; String text; final newline = raw.indexOf('\n'); if (newline > 0) { header = raw.substring(0, newline).trim(); text = raw.substring(newline + 1); } else { final colon = raw.indexOf(': '); if (colon > 0 && colon < raw.length - 2) { header = raw.substring(0, colon); text = raw.substring(colon + 2); } else { return (sender: 'Talk', roomName: null, text: raw); } } final roomSep = header.lastIndexOf(' in '); if (roomSep > 0 && roomSep + 4 < header.length) { return ( sender: header.substring(0, roomSep), roomName: header.substring(roomSep + 4), text: text, ); } return (sender: header, roomName: null, text: text); } /// The decrypted `subject` JSON of a Nextcloud push-v2 notification. /// /// Covers the full shape the notifications app emits, including the three /// delete variants which the neon `DecryptedSubject` helper only partially /// models (it lacks `delete-multiple`/`nids`). class PushSubject { /// Nextcloud notification id. final int? nid; /// App that raised the notification (e.g. `spreed` for Talk). final String? app; /// Human-readable subject line. final String? subject; /// Notification type (e.g. `chat`, `background`). final String? type; /// App-specific object id. For Talk this is the chat/room token. final String? id; final bool delete; final bool deleteMultiple; final bool deleteAll; /// Notification ids to remove for a `delete-multiple` push. final List nids; const PushSubject({ this.nid, this.app, this.subject, this.type, this.id, this.delete = false, this.deleteMultiple = false, this.deleteAll = false, this.nids = const [], }); bool get isAnyDelete => delete || deleteMultiple || deleteAll; /// True when this notification originates from Nextcloud Talk. bool get isTalk => app == 'spreed'; factory PushSubject.fromJson(Map json) { final rawNids = json['nids']; return PushSubject( nid: (json['nid'] as num?)?.toInt(), app: json['app'] as String?, subject: json['subject'] as String?, type: json['type'] as String?, id: json['id'] as String?, delete: json['delete'] == true, deleteMultiple: json['delete-multiple'] == true, deleteAll: json['delete-all'] == true, nids: rawNids is List ? rawNids .whereType() .map((e) => e is num ? e.toInt() : int.tryParse('$e')) .whereType() .toList() : const [], ); } }