# WebImageAuto

WebImageAuto is a Windows desktop project for processing images one at a time through the ChatGPT website in a browser session controlled by Playwright. It does **not** use the OpenAI API.

The current single-image flow validates and attaches one local image, confirms the prompt in the ChatGPT DOM, submits it, detects a stable image in the new Assistant response, downloads it through Playwright's download event, validates it with Pillow, and records `SUCCESS`. It intentionally does not process more than one image at a time.

## Important limitations and safety rules

- WebImageAuto automates a website UI rather than calling an API. ChatGPT UI changes can break the automation and must stop unattended processing safely.
- Login and any security verification must be completed directly by the user in the visible browser.
- The program must not bypass CAPTCHA, service limits, login security, content restrictions, or any other protection.
- A detected CAPTCHA, login requirement, usage limit, selector-wide failure, profile conflict, disk-space problem, or output-permission error must stop the whole queue.
- It does not automatically judge whether generated faces are satisfactory. Success means that a generated image was detected and a non-empty result file was saved.
- Website terms and applicable service rules remain the user's responsibility. Reliable unattended operation is not guaranteed.
- Error artifacts may include `page.html`, which can contain personal information visible in the browser. Review and redact it before sharing.

The application never stores a ChatGPT password, reads or prints browser cookies, or records full user messages, cookies, or authentication tokens in logs. `browser_profile/` and `config.json` are excluded from Git.

## Single-image scope

Implemented:

- UTF-8 Python project layout and application entry point
- JSON configuration loading with normalized `pathlib.Path` values
- SQLite `jobs`, `settings`, and `events` tables
- recovery of interrupted states to `STOPPED` on startup
- non-recursive `.jpg`, `.jpeg`, `.png`, and `.webp` input discovery
- duplicate identity using both file size and SHA-256
- collision-safe result naming and temporary-download filtering
- daily UTF-8 application logs
- minimal settings, command, status, controls, progress, and job-table GUI
- visible external Google Chrome or Microsoft Edge launched by the app and attached through loopback CDP; only Playwright Chromium fallback uses `launch_persistent_context`
- browser session data is separated under `browser_profile/chrome`, `browser_profile/msedge`, and `browser_profile/chromium`; existing root profile data is never deleted and is reported as `legacy_profile_detected`
- fixed `headless=false`, downloads allowed, output download path, and viewport
- ChatGPT navigation and message-input/login/CAPTCHA/security-state detection
- centralized ordered selector candidates with successful-selector logging
- browser dialog, navigation, page-close, context-close, and crash handlers
- all Playwright calls isolated in a dedicated Qt worker thread
- Pillow-backed input validation: existence, regular-file, supported extension, non-zero size, SHA-256, readable contents, dimensions
- one-image flow: validation, file input attachment, attachment preview/remove/send-readiness checks, prompt DOM confirmation, send-button click
- response evidence: user message count increase, new Assistant response, generation-indicator disappearance, latest Assistant image `complete`, natural dimensions, source/blob URL, and non-zero bounding box
- stable-image detection in only the newest Assistant response, scoped active-error checks, and download controls scoped to that response
- image-anchored action trigger detection for current share/export or SVG-only controls, using bottom-right spatial scoring and a short 10-second limit
- share-modal confirmation, modal-scoped final download selection, and direct-download compatibility
- Playwright download events with managed runtime-folder fallback, collision-safe result naming, file-size stability checks, and Pillow validation
- shared app/probe download collection that tolerates a zero-byte `save_as` from external Chrome/CDP, then checks the configured Chrome folder, Windows `Downloads`, and the runtime download folder without deleting native files
- `SUCCESS` persistence with `result_path`, `completed_at`, cleared error fields, and optional collision-safe source movement to `completed`
- selector-failure diagnostics under `logs/errors/YYYYMMDD_HHMMSS/`
- QThread commands and signals for single test, cancellation, stages, progress, current file, table rows, and image detection
- pytest coverage for configuration, browser lifecycle, selector lookup, login states, diagnostics, image validation, DOM actions, file queue, database, and download helpers

Not implemented yet:

- repeated processing, automatic retry, pause/resume, or whole-queue start

Connected controls are input-folder selection, photo-list refresh, table-row selection, **선택한 사진 1장 테스트**, **현재 작업 중지**, and **종료**, in addition to the Phase 2 browser controls. Whole-queue, pause, and failed-item retry controls remain disabled.

## Requirements

- Windows 10 or 11
- Python 3.12
- Playwright Chromium (fallback; installed Chrome or Edge may be selected automatically)

## Browser compatibility

The Browser settings panel selects Google Chrome (default), Microsoft Edge, or Playwright Chromium. Chrome and Edge are discovered in the standard Program Files and LOCALAPPDATA locations, started with a dedicated profile and a dynamically allocated `127.0.0.1` debugging port, then attached with Playwright `connect_over_cdp`. They are never launched with Playwright's persistent-context API. An explicit executable path takes precedence; an invalid explicit path stops immediately with `BROWSER_EXECUTABLE_INVALID` and is never replaced by fallback. The fallback order is Chrome → Edge → Playwright Chromium. Playwright Chromium remains a direct-launch fallback and is not recommended for Google login.

The browser is always visible and uses the dedicated persistent profile for the selected browser. External Chrome/Edge receives `--disable-session-crashed-bubble` only to suppress the previous-session recovery bubble after an interrupted run; it does not weaken browser security. No stealth flags, webdriver masking, user-agent spoofing, `--no-sandbox`, `--disable-web-security`, CAPTCHA bypass, or security-screen bypass is used. Login and any verification must be completed manually. The application never edits or deletes profile Preferences to suppress recovery UI and only terminates the browser process that it started, after a graceful shutdown wait.

For each enabled single-image test, a new conversation is prepared by navigating to the configured base ChatGPT URL first. The new-chat button is only a bounded fallback when direct navigation itself fails, and a web UI change can make that fallback unavailable. A test stops before attachment or prompt submission when the empty-chat state cannot be confirmed; it does not automatically continue to the next step.

ChatGPT may expose its active prompt editor as either a `textarea` or a `contenteditable` element. WebImageAuto selects a visible, enabled, on-screen editor, enters the prompt with the appropriate DOM input method, normalizes line and whitespace differences, and confirms the reflected DOM text, retained attachment, and enabled send button before sending. A confirmation failure stops without sending. If the ChatGPT UI changes, the centralized `MESSAGE_INPUT_SELECTORS` candidates in `src/selectors.py` may need maintenance.

The work tab keeps the current status, progress, one-line error summary, controls, and photo table visible. The detail tab retains at most 500 recent GUI status lines. Full Playwright exceptions and call logs are written only to `logs/app_YYYYMMDD.log`, while screenshots, `page.html`, and structured `error.json` evidence are stored under `logs/errors`. Review `page.html` for personal information before sharing it outside your machine, and use the Exit button for a normal worker and browser shutdown.

During a single-image job, the status area updates the total elapsed time and current-stage elapsed time once per second using a monotonic clock. While the current stage is image detection it also shows the configured maximum generation wait and remaining time. The final elapsed values remain visible after success, failure, or cancellation.

Generated-image detection checks the newest Assistant response, its response wrapper and following sibling, newly added page images, and image containers connected to new download controls. Small layout changes and refreshed blob URLs do not reset stability. A download icon connected to a complete image is treated as a strong completion signal. If an image is visibly complete but detection does not advance, inspect `IMAGE_DETECTION_PROGRESS` in the detailed application log and the privacy-limited timeout evidence under `logs/errors`.

Prompt diagnostics record selector/type/state booleans and text lengths only; the full prompt is not stored in `error.json` or application log messages. The captured `page.html` can still contain visible page content, so review it before sharing.

## Installation

From PowerShell in this project directory:

```powershell
python -m venv .venv
.venv\Scripts\activate
python -m pip install --upgrade pip
pip install -r requirements.txt
python -m playwright install chromium
```

Copy `config.example.json` to `config.json` only when you want local settings that differ from the defaults. `config.json` is intentionally ignored by Git.

## Run

```powershell
python app.py
```

The browser is always visible (`headless: false`) so the user can log in and handle any security screen directly. Click **브라우저 열기** to start the persistent context and open `chatgpt_url`. After completing login manually, click **로그인 확인**. Click **브라우저 닫기** before exiting when possible; closing the application also requests orderly context cleanup.

The application never fills login credentials. CAPTCHA and security-verification screens are detected and reported as stop-required states; they are never bypassed. A website UI change can exhaust all selector candidates, save diagnostics, and stop the operation.

## Test

```powershell
pytest -q
```

The tests use disposable temporary files, databases, and Playwright test doubles. They do not access ChatGPT or launch a real browser.

## Configuration

The defaults in `config.example.json` are:

| Key | Purpose |
| --- | --- |
| `chatgpt_url` | ChatGPT page to open later |
| `input_dir` | Direct-child image files to scan |
| `output_dir` | Completed generated downloads |
| `completed_dir` | Source files moved after success |
| `failed_dir` | Optional source files moved after failure |
| `browser_profile_dir` | Persistent browser session data |
| `browser_type` | `chrome`, `msedge`, or `chromium` (default `chrome`) |
| `browser_executable_path` | Optional explicit browser executable; blank enables discovery |
| `allow_browser_fallback` | Try the remaining browser order when the requested browser fails |
| `browser_start_timeout_seconds` | Time allowed for the loopback CDP endpoint to become ready (default `30`) |
| `new_chat_timeout_seconds` | Maximum new-chat navigation/button/state-confirmation wait (default `15`) |
| `prompt_input_timeout_seconds` | Maximum time for prompt DOM reflection and one alternate input attempt (default `15`) |
| `prompt_confirmation_poll_seconds` | Delay between prompt DOM confirmation checks (default `0.25`) |
| `delay_between_jobs_seconds` | Delay between separate images |
| `max_generation_wait_seconds` | Upper limit for generation detection |
| `upload_timeout_seconds` | Future upload deadline |
| `download_timeout_seconds` | Maximum Playwright download-event wait |
| `download_event_grace_seconds` | Short wait for the Playwright event before native-folder fallback |
| `download_poll_interval_seconds` | Native file and size-stability polling interval |
| `download_stable_checks` | Required consecutive identical file-size observations |
| `runtime_download_directory` | Project-local native download fallback directory |
| `response_start_timeout_seconds` | Maximum wait for a new Assistant response |
| `image_detect_timeout_seconds` | Maximum wait for a complete image in that response |
| `image_stable_check_interval_seconds` | Interval between generated-image size stability checks |
| `image_stable_required_count` | Required consecutive stable image observations |
| `dom_stable_seconds` | Minimum stable DOM/image duration |
| `browser_graceful_shutdown_seconds` | Grace period before terminating only the app-owned browser process |
| `retry_count` | Recoverable retry count |
| `headless` | Whether the future browser is hidden; default is `false` |
| `use_new_chat_for_each_image` | Start each image in a fresh chat |
| `move_source_after_success` | Move successful source files |
| `move_source_after_failure` | Move failed source files |

## Data and logging

Runtime data is stored under `data/`; daily logs use `logs/app_YYYYMMDD.log`. Generated runtime content is excluded from Git while placeholder files retain the directory structure. Logs record browser start/stop, current URL, selected selector, login-check result, and error code/message, but not cookies, tokens, passwords, or full user messages.

If every message-input selector fails, the program creates a timestamped directory under `logs/errors/` containing:

- `screenshot.png`
- `page.html`
- `error.json` with timestamp, job/stage/error metadata, selector candidates, sanitized current URL, and before/after message/image counts

`page.html` and the screenshot can contain personal information visible on the page. Inspect and redact them before sharing. The program does not query or export cookies or authentication tokens.

Job states include `PENDING`, `UPLOADING`, `UPLOADED`, `SENDING`, `GENERATING`, `IMAGE_DETECTED`, `DOWNLOADING`, `SUCCESS`, `RETRY_WAIT`, `FAILED`, and `STOPPED`. `SUCCESS` is written only after a download event or managed runtime file is confirmed, saved, size-checked, and validated by Pillow.

In an external Chrome/CDP session, a Playwright download event does not by itself prove that usable bytes were saved. `download.save_as()` may return after creating a zero-byte file while Chrome writes the valid image to its native Downloads folder. WebImageAuto deletes the empty temporary file, watches the configured Chrome directory, Windows `Downloads`, and the project runtime download directory, then copies (never moves or deletes) a stable native file into `output`. Success is recorded only after three stable-size observations, an exact copy-size match, Pillow `verify()` plus reopen/`load()`, and atomic finalization using the image's actual format.

The independent `tools/test_current_image_download.py` probe uses the same shared file collector as the main single-image flow. It can diagnose the current completed image download without uploading, sending a prompt, generating a new image, changing the job database, or closing the external browser.

The final single-image events include `IMAGE_STABLE`, `IMAGE_ACTION_BUTTON_FOUND`, `SHARE_MODAL_FOUND`, `FINAL_DOWNLOAD_BUTTON_FOUND`, `DOWNLOAD_STARTED`, `DOWNLOAD_EVENT_RECEIVED` or `RUNTIME_DOWNLOAD_FILE_FOUND`, `DOWNLOAD_SAVED`, `DOWNLOAD_VALIDATED`, and `JOB_SUCCESS`. The image action trigger is clicked without a long `expect_download`; `expect_download` is registered immediately before the modal's final download button.

Future recoverable errors may retry up to the configured limit. Stop-required errors must halt the queue and notify the user rather than advancing to another image.

## Manual single-image verification

1. Run the program and click **브라우저 열기**.
2. Complete login manually and click **로그인 확인**.
3. Select one photo and click **선택한 사진 1장 테스트**.
4. Confirm the prompt was sent and a new image appears in the latest Assistant response.
5. Confirm the image is downloaded automatically into `output` and opens normally.
6. When `move_source_after_success=true`, confirm the source moved to `completed`.
7. Confirm the table state is `SUCCESS`, success count is 1, progress is 100%, and the result filename/completion time are shown.
8. Exit with **종료**, then restart and confirm Chrome does not show the previous-session recovery bubble.

The current-work cancel button remains enabled throughout validation, upload, prompt entry, response/image waits, download, and validation. A user cancellation records `STOPPED`/`USER_CANCELLED` without presenting it as a general error; the browser remains open for inspection.

The download stages are shown as download-button search, download start, file save, and file validation. A visible image is not `SUCCESS` by itself: the result must be saved under `output` and pass file and Pillow validation first.

## Windows release build

Install the development dependencies, then run:

```powershell
powershell -ExecutionPolicy Bypass -File .\build_release.ps1
```

The script creates the one-folder bundle at `dist\WebImageAuto\` and the distributable archive at `release\WebImageAuto-1.0.1-win64.zip` by default. Pass `-Version` to create another release version. Runtime configuration, logs, database, and user-selected folders are stored under `%LOCALAPPDATA%\WebImageAuto` in a packaged build. The generated executable is `dist\WebImageAuto\WebImageAuto.exe`.
