Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 56 additions & 1 deletion electron/main.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,11 @@ ipcMain.handle('dialog:openFile', async () => {
return {
path: filePath,
name: fileName,
buffer: fileContent.buffer,
// Slice the buffer to avoid sending the entire Node.js Buffer pool
buffer: fileContent.buffer.slice(
fileContent.byteOffset,
fileContent.byteOffset + fileContent.byteLength,
),
};
});

Expand Down Expand Up @@ -227,6 +231,57 @@ ipcMain.handle('fs:fileExists', async (_event, filePath) => {
return fs.existsSync(filePath);
});

// Read file from absolute path (for persistence)
ipcMain.handle('file:readFromPath', async (_event, filePath) => {
try {
if (typeof filePath !== 'string') {
return { success: false, error: 'Invalid file path' };
}
const normalized = path.resolve(filePath);
// Require absolute path and reject path traversal segments
if (!path.isAbsolute(filePath)) {
return { success: false, error: 'Invalid file path' };
}
if (filePath.split(/[/\\]+/).includes('..')) {
return { success: false, error: 'Invalid file path' };
}
if (path.extname(normalized).toLowerCase() !== '.zip') {
return { success: false, error: 'Only .zip files are allowed' };
}

// Pre-check with lstat to reject symlinks (works cross-platform including Windows)
const lst = await fs.promises.lstat(normalized);
if (!lst.isFile() || lst.isSymbolicLink()) {
return { success: false, error: 'Path is not a regular file' };
}

// Use O_NOFOLLOW when available to reject symlinks atomically (not supported on Windows)
const openFlags =
typeof fs.constants.O_NOFOLLOW === 'number'
? fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW
: fs.constants.O_RDONLY;
const fd = await fs.promises.open(normalized, openFlags);
try {
const content = await fd.readFile();
return {
success: true,
buffer: content.buffer.slice(
content.byteOffset,
content.byteOffset + content.byteLength,
),
name: path.basename(normalized),
};
} finally {
await fd.close();
}
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : String(error),
};
}
});

ipcMain.handle('shell:openExternal', async (_event, url) => {
// Validate URL before opening
if (!url.startsWith('https://github.com/rodrigogs/whats-reader')) {
Expand Down
4 changes: 4 additions & 0 deletions electron/preload.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ contextBridge.exposeInMainWorld('electronAPI', {
readDir: (dirPath) => ipcRenderer.invoke('fs:readDir', dirPath),
fileExists: (filePath) => ipcRenderer.invoke('fs:fileExists', filePath),

// Persistence file operations
readFileFromPath: (filePath) =>
ipcRenderer.invoke('file:readFromPath', filePath),

// External links
openExternal: (url) => ipcRenderer.invoke('shell:openExternal', url),

Expand Down
5 changes: 5 additions & 0 deletions examples/parser-tests/asian-ampm-format-test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
2024/12/01, 10:04 pm - Alice: Messages and calls are end-to-end encrypted. Only people in this chat can read, listen to, or share them. Learn more.
2024/12/01, 10:05 pm - Alice: Hello there
2024/12/01, 10:06 pm - Bob: Hi!
2024/12/01, 10:07 pm - Alice: <Media omitted>
2024/12/01, 10:08 pm - Bob: Nice
3 changes: 3 additions & 0 deletions examples/parser-tests/bracketed-year-first-test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[2018/06/13, 21:25:15] Ana: mensagem com colchetes
[2018/06/13 1:55:00 p. m.] Bruno: ampm com espaços
[2018/06/13, 01:56:00 AM] Ana: uppercase também funciona
3 changes: 3 additions & 0 deletions examples/parser-tests/dot-time-klo-format-test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
13.6.2018 klo 21.25.15 - Mika: tämä on testi
13.06.18 21.25.15: Laura: tämä on toinen testi
13.06.2018, 21.25 - Mika: ilman sekunteja
4 changes: 4 additions & 0 deletions examples/parser-tests/european-ampm-format-test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
23/06/2018, 1:55 p.m. - Loris: one
23/06/2018, 1:56 p. m. - Luke: two
23/06/2018, 1:57 PM - Loris: three
23/06/2018, 1:58 am - Luke: four
26 changes: 24 additions & 2 deletions messages/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@
"update_never_tooltip": "Nie Update-Benachrichtigungen anzeigen",
"version_view_releases": "Versionen auf GitHub anzeigen",
"close": "Schließen",
"close_modal": "Modal schließen",
"update_ready": "Update bereit",
"update_downloading": "Update wird heruntergeladen",
"update_close": "Aktualisierungsbenachrichtigung schließen",
Expand Down Expand Up @@ -241,5 +242,26 @@
"media_gallery_type_other": "Andere",
"media_gallery_clear_filter": "Filter löschen",
"media_gallery_participant_search_placeholder": "Suche nach Name oder Nummer...",
"media_gallery_participant_no_match": "Kein Teilnehmer entspricht \"{query}\""
}
"persistence_remember_conversation": "Unterhaltung Merken",
"persistence_conversation_saved": "Die Unterhaltung wird gespeichert",
"persistence_conversation_removed": "Unterhaltung aus gespeicherten Sitzungen entfernt",
"persistence_restore_title": "Gespeicherte Unterhaltungen Wiederherstellen",
"persistence_restore_description": "Sie haben gespeicherte Unterhaltungen aus früheren Sitzungen:",
"persistence_restore_button": "Ausgewählte Wiederherstellen ({count})",
"persistence_start_fresh": "Neu Beginnen",
"persistence_dont_show_again": "Nicht mehr anzeigen",
"persistence_last_opened": "Zuletzt geöffnet: {date}",
"persistence_message_count": "{count} Nachrichten",
"persistence_reselect_title": "Bitte Datei Erneut Auswählen",
"persistence_reselect_description": "Um \"{chatTitle}\" wiederherzustellen, wählen Sie bitte die Originaldatei aus:",
"persistence_skip": "Überspringen",
"persistence_select_all": "Alle auswählen",
"persistence_deselect_all": "Alle abwählen",
"persistence_drop_zip": "WhatsApp-ZIP-Datei hier ablegen",
"persistence_or_browse": "oder klicken Sie unten zum Durchsuchen",
"persistence_browse_files": "Dateien durchsuchen",
"persistence_save_failed": "Fehler beim Speichern der Unterhaltung",
"persistence_restore_failed": "Fehler beim Wiederherstellen der Unterhaltung",
"persistence_remove_failed": "Fehler beim Entfernen der Unterhaltung",
"close_notification": "Benachrichtigung schließen"
}
25 changes: 24 additions & 1 deletion messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@
"calendar_weekday_fri": "Fr",
"calendar_weekday_sat": "Sa",
"close": "Close",
"close_modal": "Close modal",
"media_gallery_options": "Options",
"media_gallery_filter_by_participant": "Filter by participant",
"media_gallery_filter_by_type": "Filter by type",
Expand All @@ -241,5 +242,27 @@
"media_gallery_type_other": "Other",
"media_gallery_clear_filter": "Clear filter",
"media_gallery_participant_search_placeholder": "Search by name or number...",
"media_gallery_participant_no_match": "No participant matches \"{query}\""
"media_gallery_participant_no_match": "No participant matches \"{query}\"",
"persistence_remember_conversation": "Remember Conversation",
"persistence_conversation_saved": "Conversation will be remembered",
"persistence_conversation_removed": "Conversation removed from saved sessions",
"persistence_restore_title": "Restore Saved Conversations",
"persistence_restore_description": "You have saved conversations from previous sessions:",
"persistence_restore_button": "Restore Selected ({count})",
"persistence_start_fresh": "Start Fresh",
"persistence_dont_show_again": "Don't show this again",
"persistence_last_opened": "Last opened: {date}",
"persistence_message_count": "{count} messages",
"persistence_reselect_title": "Please Re-select File",
"persistence_reselect_description": "To restore \"{chatTitle}\", please select the original file:",
"persistence_skip": "Skip",
"persistence_select_all": "Select All",
"persistence_deselect_all": "Deselect All",
"persistence_drop_zip": "Drop WhatsApp ZIP file here",
"persistence_or_browse": "or click below to browse",
"persistence_browse_files": "Browse Files",
"persistence_save_failed": "Failed to save conversation",
"persistence_restore_failed": "Failed to restore conversation",
"persistence_remove_failed": "Failed to remove conversation",
"close_notification": "Close notification"
}
26 changes: 24 additions & 2 deletions messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@
"update_never_tooltip": "Nunca mostrar notificaciones de actualización",
"version_view_releases": "Ver versiones en GitHub",
"close": "Cerrar",
"close_modal": "Cerrar modal",
"update_ready": "Actualización lista",
"update_downloading": "Descargando actualización",
"update_close": "Cerrar notificación de actualización",
Expand Down Expand Up @@ -241,5 +242,26 @@
"media_gallery_type_other": "Otro",
"media_gallery_clear_filter": "Limpiar filtro",
"media_gallery_participant_search_placeholder": "Buscar por nombre o número...",
"media_gallery_participant_no_match": "Ningún participante coincide con \"{query}\""
}
"persistence_remember_conversation": "Recordar Conversación",
"persistence_conversation_saved": "La conversación será recordada",
"persistence_conversation_removed": "Conversación eliminada de las sesiones guardadas",
"persistence_restore_title": "Restaurar Conversaciones Guardadas",
"persistence_restore_description": "Tienes conversaciones guardadas de sesiones anteriores:",
"persistence_restore_button": "Restaurar Seleccionadas ({count})",
"persistence_start_fresh": "Empezar de Nuevo",
"persistence_dont_show_again": "No mostrar de nuevo",
"persistence_last_opened": "Última apertura: {date}",
"persistence_message_count": "{count} mensajes",
"persistence_reselect_title": "Por Favor, Selecciona el Archivo de Nuevo",
"persistence_reselect_description": "Para restaurar \"{chatTitle}\", selecciona el archivo original:",
"persistence_skip": "Omitir",
"persistence_select_all": "Seleccionar Todo",
"persistence_deselect_all": "Deseleccionar Todo",
"persistence_drop_zip": "Suelta el archivo ZIP de WhatsApp aquí",
"persistence_or_browse": "o haz clic abajo para buscar",
"persistence_browse_files": "Buscar Archivos",
"persistence_save_failed": "Error al guardar la conversación",
"persistence_restore_failed": "Error al restaurar la conversación",
"persistence_remove_failed": "Error al eliminar la conversación",
"close_notification": "Cerrar notificación"
}
26 changes: 24 additions & 2 deletions messages/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@
"update_never_tooltip": "Ne jamais afficher les notifications de mise à jour",
"version_view_releases": "Voir les versions sur GitHub",
"close": "Fermer",
"close_modal": "Fermer la boîte de dialogue",
"update_ready": "Prêt pour la mise à jour",
"update_downloading": "Téléchargement de la mise à jour",
"update_close": "Fermer la notification de mise à jour",
Expand Down Expand Up @@ -241,5 +242,26 @@
"media_gallery_type_other": "Autre",
"media_gallery_clear_filter": "Effacer le filtre",
"media_gallery_participant_search_placeholder": "Recherche par nom ou numéro...",
"media_gallery_participant_no_match": "Aucun participant ne correspond à \"{query}\""
}
"persistence_remember_conversation": "Mémoriser la Conversation",
"persistence_conversation_saved": "La conversation sera mémorisée",
"persistence_conversation_removed": "Conversation supprimée des sessions enregistrées",
"persistence_restore_title": "Restaurer les Conversations Sauvegardées",
"persistence_restore_description": "Vous avez des conversations sauvegardées de sessions précédentes :",
"persistence_restore_button": "Restaurer la Sélection ({count})",
"persistence_start_fresh": "Repartir à Zéro",
"persistence_dont_show_again": "Ne plus afficher",
"persistence_last_opened": "Dernière ouverture : {date}",
"persistence_message_count": "{count} messages",
"persistence_reselect_title": "Veuillez Resélectionner le Fichier",
"persistence_reselect_description": "Pour restaurer \"{chatTitle}\", veuillez sélectionner le fichier original :",
"persistence_skip": "Ignorer",
"persistence_select_all": "Tout sélectionner",
"persistence_deselect_all": "Tout désélectionner",
"persistence_drop_zip": "Déposez le fichier ZIP WhatsApp ici",
"persistence_or_browse": "ou cliquez ci-dessous pour parcourir",
"persistence_browse_files": "Parcourir les fichiers",
"persistence_save_failed": "Échec de l'enregistrement de la conversation",
"persistence_restore_failed": "Échec de la restauration de la conversation",
"persistence_remove_failed": "Échec de la suppression de la conversation",
"close_notification": "Fermer la notification"
}
26 changes: 24 additions & 2 deletions messages/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@
"update_never_tooltip": "Non mostrare mai notifiche di aggiornamento",
"version_view_releases": "Visualizza le versioni su GitHub",
"close": "Chiudi",
"close_modal": "Chiudi finestra modale",
"update_ready": "Aggiornamento pronto",
"update_downloading": "Download dell'aggiornamento",
"update_close": "Chiudi notifica di aggiornamento",
Expand Down Expand Up @@ -241,5 +242,26 @@
"media_gallery_type_other": "Altro",
"media_gallery_clear_filter": "Cancella filtro",
"media_gallery_participant_search_placeholder": "Cerca per nome o numero...",
"media_gallery_participant_no_match": "Nessun partecipante corrisponde a \"{query}\""
}
"persistence_remember_conversation": "Ricorda Conversazione",
"persistence_conversation_saved": "La conversazione verrà ricordata",
"persistence_conversation_removed": "Conversazione rimossa dalle sessioni salvate",
"persistence_restore_title": "Ripristina Conversazioni Salvate",
"persistence_restore_description": "Hai conversazioni salvate da sessioni precedenti:",
"persistence_restore_button": "Ripristina Selezionate ({count})",
"persistence_start_fresh": "Ricomincia da Zero",
"persistence_dont_show_again": "Non mostrare più",
"persistence_last_opened": "Ultima apertura: {date}",
"persistence_message_count": "{count} messaggi",
"persistence_reselect_title": "Seleziona Nuovamente il File",
"persistence_reselect_description": "Per ripristinare \"{chatTitle}\", seleziona il file originale:",
"persistence_skip": "Salta",
"persistence_select_all": "Seleziona tutto",
"persistence_deselect_all": "Deseleziona tutto",
"persistence_drop_zip": "Trascina qui il file ZIP di WhatsApp",
"persistence_or_browse": "o clicca sotto per sfogliare",
"persistence_browse_files": "Sfoglia file",
"persistence_save_failed": "Impossibile salvare la conversazione",
"persistence_restore_failed": "Impossibile ripristinare la conversazione",
"persistence_remove_failed": "Impossibile rimuovere la conversazione",
"close_notification": "Chiudi notifica"
}
26 changes: 24 additions & 2 deletions messages/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@
"update_never_tooltip": "アップデート通知を表示しない",
"version_view_releases": "GitHubでリリースを見る",
"close": "閉じる",
"close_modal": "モーダルを閉じる",
"update_ready": "アップデート準備完了",
"update_downloading": "アップデートをダウンロード中",
"update_close": "更新通知を閉じる",
Expand Down Expand Up @@ -241,5 +242,26 @@
"media_gallery_type_other": "他の",
"media_gallery_clear_filter": "フィルターをクリア",
"media_gallery_participant_search_placeholder": "名前または番号で検索...",
"media_gallery_participant_no_match": "「{query}」に一致する参加者はいません"
}
"persistence_remember_conversation": "会話を記憶",
"persistence_conversation_saved": "会話が記憶されます",
"persistence_conversation_removed": "保存済みセッションから会話が削除されました",
"persistence_restore_title": "保存済みの会話を復元",
"persistence_restore_description": "以前のセッションから保存された会話があります:",
"persistence_restore_button": "選択した会話を復元 ({count})",
"persistence_start_fresh": "新しく始める",
"persistence_dont_show_again": "今後表示しない",
"persistence_last_opened": "最終オープン: {date}",
"persistence_message_count": "{count} 件のメッセージ",
"persistence_reselect_title": "ファイルを再選択してください",
"persistence_reselect_description": "\"{chatTitle}\" を復元するには、元のファイルを選択してください:",
"persistence_skip": "スキップ",
"persistence_select_all": "すべて選択",
"persistence_deselect_all": "すべて解除",
"persistence_drop_zip": "WhatsAppのZIPファイルをここにドロップ",
"persistence_or_browse": "または下をクリックして参照",
"persistence_browse_files": "ファイルを参照",
"persistence_save_failed": "会話の保存に失敗しました",
"persistence_restore_failed": "会話の復元に失敗しました",
"persistence_remove_failed": "会話の削除に失敗しました",
"close_notification": "通知を閉じる"
}
26 changes: 24 additions & 2 deletions messages/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@
"update_never_tooltip": "Nooit updatemeldingen tonen",
"version_view_releases": "Bekijk releases op GitHub",
"close": "Sluiten",
"close_modal": "Venster sluiten",
"update_ready": "Update gereed",
"update_downloading": "Update downloaden",
"update_close": "Sluit de updatemelding",
Expand Down Expand Up @@ -241,5 +242,26 @@
"media_gallery_type_other": "Ander",
"media_gallery_clear_filter": "Filter wissen",
"media_gallery_participant_search_placeholder": "Zoeken op naam of nummer...",
"media_gallery_participant_no_match": "Geen deelnemer komt overeen met \"{query}\""
}
"persistence_remember_conversation": "Gesprek Onthouden",
"persistence_conversation_saved": "Het gesprek wordt onthouden",
"persistence_conversation_removed": "Gesprek verwijderd uit opgeslagen sessies",
"persistence_restore_title": "Opgeslagen Gesprekken Herstellen",
"persistence_restore_description": "U heeft opgeslagen gesprekken van eerdere sessies:",
"persistence_restore_button": "Geselecteerde Herstellen ({count})",
"persistence_start_fresh": "Opnieuw Beginnen",
"persistence_dont_show_again": "Niet meer tonen",
"persistence_last_opened": "Laatst geopend: {date}",
"persistence_message_count": "{count} berichten",
"persistence_reselect_title": "Selecteer het Bestand Opnieuw",
"persistence_reselect_description": "Om \"{chatTitle}\" te herstellen, selecteer het originele bestand:",
"persistence_skip": "Overslaan",
"persistence_select_all": "Alles selecteren",
"persistence_deselect_all": "Alles deselecteren",
"persistence_drop_zip": "WhatsApp ZIP-bestand hier neerzetten",
"persistence_or_browse": "of klik hieronder om te bladeren",
"persistence_browse_files": "Bestanden bladeren",
"persistence_save_failed": "Gesprek opslaan mislukt",
"persistence_restore_failed": "Gesprek herstellen mislukt",
"persistence_remove_failed": "Gesprek verwijderen mislukt",
"close_notification": "Melding sluiten"
}
Loading