dimage 2025.7.7
Downloads all media assets from JSON files generated by DiscordChatExporter.
Requires Python: >=3.9

# Dimage

[![Build](https://github.com/Inc44/Dimage/actions/workflows/build.yml/badge.svg)](https://github.com/Inc44/Dimage/actions/workflows/build.yml)

Downloads all media assets from JSON files generated by [DiscordChatExporter](https://github.com/Tyrrrz/DiscordChatExporter).

## ⚙️ Features

- Parses `.json` export files from DiscordChatExporter.
- Downloads guild icons, user avatars, mention avatars, reaction avatars, custom emojis (inline and in reactions), and message attachments.
- Organizes downloaded media into subdirectories named after the source channel or DM.
- Optionally categorizes media into folders (`icons`, `avatars`, `emojis`, `channels`) using a command-line flag.
- Sets local file modification timestamps to match the original Discord message timestamps.
- Skips duplicate URLs to avoid redundant downloads and improve performance.
- Offers fine-grained control over which asset types to download via command-line flags.
- Supports skipping downloads based on file extensions.
- Includes a mode to set timestamps on existing files without downloading.

## ⚠️ Disclaimers

- **Default File Extension**: If a file extension cannot be determined from the URL, the tool defaults to `.png` for icons, avatars, and standard emojis. Animated emojis default to `.gif`.
- **Timestamp-Only Mode**: The `--timestamp-only` mode assumes the file structure and naming have not been altered since the initial download. It relies on a processing order that may be unstable if files have been moved, renamed, or deleted. Use this feature only to refresh timestamps on an unmodified collection of downloaded files.
- **Testing**: This software is tested manually. Automated testing of download functionality is not feasible because Discord media links expire after 24 hours.

## 🚀 Installation

### From PyPI
```bash
pip install dimage
```

### With pipx
```bash
pipx install dimage
```

### With Conda
```bash
conda create -n dimage python=3.9 -y
conda activate dimage
pip install dimage
```

### From Source
```bash
git clone https://github.com/Inc44/Dimage.git
cd Dimage
```

To install the package:
```bash
pip install .
```

To install only the dependencies:
```bash
pip install -r requirements.txt
```

_If you install only the dependencies, run the program using `python -m dimage.cli` (or `python -OO dimage/cli.py`) instead of the `dimage` command._

## 🛠️ Build
```bash
pip install build
python -m build
```

## 📖 Usage Examples

### Standard Download
Download all media, skipping duplicates and SVG files. Media will be organized into folders named after each channel. Assumes your JSON files are in a directory named `json`.
```bash
dimage --no-dupes --skip svg
```

### Organized Download
Download all media, skipping duplicates and SVG files. Media will be organized by type (`icons`, `avatars`, `emojis`, `channels`).
```bash
dimage --no-dupes --skip svg --organize
```

### Update Timestamps Only
Apply original timestamps to already downloaded files. Assumes files are in an organized structure. No new files will be downloaded. Displays info and warnings.
```bash
dimage --no-dupes --skip svg --timestamp-only --organize --loglevel info
```

### Attachments Only
Download only message attachments, skipping all other media types, duplicates, and SVG files.
```bash
dimage --no-guild-icon --no-avatars --no-mentions --no-reactions --no-reactions-emojis --no-inline-emojis --no-dupes --skip svg
```
_I prefer to skip SVG files, as in 99.99% of cases, they are just standard Discord emojis._

## 🎨 Command-Line Arguments

| Argument                | Description                                                                                          |
|-------------------------|------------------------------------------------------------------------------------------------------|
| `-i, --input <path>`    | Path to the input directory containing `.json` files. Default: `json`.                               |
| `-o, --output <path>`   | Path to the root output directory for downloads. Default: `downloads`.                               |
| `--no-guild-icon`       | Skip downloading the guild/server icon.                                                              |
| `--no-avatars`          | Skip downloading message author avatars.                                                             |
| `--no-mentions`         | Skip downloading avatars of mentioned users.                                                         |
| `--no-reactions`        | Skip downloading avatars of users who reacted.                                                       |
| `--no-reactions-emojis` | Skip downloading custom emojis used in reactions.                                                    |
| `--no-inline-emojis`    | Skip downloading custom emojis used inline in messages.                                              |
| `--no-attachments`      | Skip downloading message attachments.                                                                |
| `--no-dupes`            | Avoid downloading duplicate files based on URL.                                                      |
| `--skip <exts>`         | Skip files with specified comma-separated extensions.                                                |
| `--timestamp-only`      | Set timestamps on existing files without downloading.                                                |
| `--organize`            | Organize files into categories: `icons`, `avatars`, `emojis`, and `channels` (for attachments).      |
| `--loglevel <level>`    | Set the logging level (`NOTSET`, `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`). Default: `ERROR`. |

## 🚧 TODO

- [ ] **Refactor for Concurrency**: Re-architect the download logic using `asyncio` and `aiohttp` to replace the current sequential, blocking `requests` implementation. This will significantly improve performance.
- [ ] **Implement Persistent Cache**: Replace the in-memory `visited_urls` set with a persistent on-disk cache (e.g., a simple database or a state file). This will allow the application to be stopped and resumed, prevent redundant downloads, and enable recovery from interruptions.
- [ ] **Add Robust Retry Logic**: Integrate a library such as `tenacity` or `retrying` into the download function to automatically handle transient network errors, timeouts, and HTTP 429 (Too Many Requests) status codes with exponential backoff.
- [ ] **Establish Formal Testing**: Develop a formal test suite using `pytest`. Implement mocking for network requests to test application logic without relying on expiring Discord URLs. Add `coverage` to measure test effectiveness and identify untested code paths.

## 📜 License

[![MIT](https://img.shields.io/badge/License-MIT-lightgrey.svg)](https://opensource.org/licenses/MIT)

This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.

## 🤝 Support

[![BuyMeACoffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-ffdd00?style=for-the-badge&logo=buy-me-a-coffee&logoColor=black)](https://buymeacoffee.com/xamituchido)
[![Ko-Fi](https://img.shields.io/badge/Ko--fi-F16061?style=for-the-badge&logo=ko-fi&logoColor=white)](https://ko-fi.com/inc44)
[![Patreon](https://img.shields.io/badge/Patreon-F96854?style=for-the-badge&logo=patreon&logoColor=white)](https://www.patreon.com/Inc44)