Bubbly – Bubble your chats
Bubbly generates an interactive HTML chat view with search, filters, time filtering, and media previews for chats from different messengers/exports.
Limitation:
- No print/PDF report export functionality is included in Bubbly.
- Bubbly is focussed on fast and easy visualization of messages themselves. BUT: It shows not all data. It will not be shown if messages are forwarded or answered. Also reactions are not visualized.
It currently supports:
- WhatsApp chat exports (iOS and Android)
- Telegram Desktop exports (JSON)
- Wire Messenger unencrypted backups (.wbu/.zip with .binpb files).
- Threema Messenger backups (CSV-based backup folder/zip).
- Generic JSON chat exports (single JSON or multiple JSONs in a folder/zip).
Interactive menu (guided setup):
python messenger/bubbly/bubbly_launcher.py --interactiveYou can also start without arguments and Bubbly will open the interactive menu automatically.
CLI usage (all required args on the CLI):
python messenger/bubbly/bubbly_launcher.py \
--parser whatsapp_export \
--input /path/to/chat_export.zip \
--output /path/to/output \
--creator "Analyst Name" \
--case CASE-123 \
--logo /path/to/logo.png \
--parser_args platform=android wa_account_name="Owner Name" wa_account_number="+123"Short flag equivalent:
python messenger/bubbly/bubbly_launcher.py \
-p whatsapp_export \
-i /path/to/chat_export.zip \
-o /path/to/output \
-u "Analyst Name" \
-k CASE-123 \
--logo /path/to/logo.png \
-a platform=android wa_account_name="Owner Name" wa_account_number="+123"Config file usage (optional default_conf.json next to the launcher, or --config to point to another file). CLI args override config values. parser_args merges config and CLI (CLI wins on conflicts).
python messenger/bubbly/bubbly_launcher.py --config /path/to/config.jsonExample config:
{
"parser": "whatsapp_export",
"input": "/path/to/chat_export.zip",
"output": "/path/to/output",
"creator": "Analyst Name",
"case": "CASE-123",
"logo": "/path/to/logo.png",
"split_by_chat": true,
"parser_args": {
"platform": "android",
"wa_account_name": "Owner Name",
"wa_account_number": "+123",
"chat_name": "Chat Export"
}
}Notes:
--parser/-pmust be one of:whatsapp_export,telegram_desktop_export,wire_messenger_backup,threema_messenger_backup,generic_json.- Split-by-chat export is the default. Use
--no-split-by-chat(or configsplit_by_chat: false) for a single merged HTML. parser_argsare parser-specific.- For WhatsApp Chat Exports:
platform,wa_account_name(optional),wa_account_number(optional),chat_name(optional). - For Telegram Desktop exports (JSON):
tg_account_name. - For Wire Messenger backups: no parser-specific args. Only unencrypted backups are supported.
- For Threema Messenger backups:
threema_account_name(optional). Threema exports are typically password-encrypted ZIP files; Bubbly currently expects the already decrypted/extracted backup contents (CSV/media files). - For Generic JSON:
json_file(optional),messages_key(optional),metadata_key(optional),account_name(optional).
- For WhatsApp Chat Exports:
You can provide:
- A single
.jsonfile - A folder or zip containing one or more
.jsonfiles (each JSON is treated as a chat and merged into one report)
Required (per message):
sendertimestamp(ISO 8601, seconds required; timezone optional)content(ortext/message)
Optional (per message):
media(string path or object/list containing a path)urlis_ownerchat(orchat_name)
Optional (top-level):
chat_namemetadata(object)sourceplatform
Minimal example:
{
"chat_name": "Example Chat",
"messages": [
{
"sender": "Alice",
"timestamp": "2026-02-01T12:34:56Z",
"content": "Hello"
}
]
}For non-programmers, you can create a Bubbly-compatible JSON from a CSV file (that is a possible output from different tools and sqlitebrowser) using:
python csv_to_bubbly_json.py \
--csv /path/to/messages.csv \
--output /path/to/chat.json \
--messenger whatsapp \
--source "SQLite export" \
--chat_name "Team Chat" \
--map sender=person timestamp=ts content=message media=file_pathHow mapping works:
- Left side is the Bubbly message field.
- Right side is your CSV column name.
Accepted mapping target fields:
- Mandatory:
sender,timestamp,content - Optional:
media,url,is_owner,chat
Field meanings:
sender: message author name.timestamp: message timestamp (ISO 8601, seconds required).content: message text.media: media file path/name (optional).url: URL linked to the message (optional).is_owner: whether this message is from the owner/account (true/false,1/0,yes/noaccepted).chat: chat name per message (optional, useful for merged datasets).
Example:
sender=personmeans CSV columnpersonbecomes JSON fieldsender.
Notes:
timestampvalues must already be ISO 8601 (for example2026-02-01T12:34:56; format%Y-%m-%dT%H:%M:%S).- Rows missing required values are skipped by default.
- Use
--strictto fail instead of skipping invalid rows. - Script doesn't take care of media file - The media must be copied to the path given in the column media -> should be relative to the newly created json.
To create this CSV you can use DB Browser for SQLite or run a query directly:
sqlite3 /path/to/db.sqlite -header -csv \
"SELECT person, ts, message, file_path FROM messages;" \
> /tmp/messages.csvBuild scripts and build-only dependencies are in build/.
See build/README.md for Linux and Windows executable build instructions.
