# ddg-rs

DuckDuckGo from the terminal — a Rust port of [`jarun/ddgr`](https://github.com/jarun/ddgr) with an interactive CAPTCHA solver. Crate is `ddg-rs`; binary is `ddg`.

It POSTs to the no-JS endpoint at `https://html.duckduckgo.com/html/`, parses the HTML for results, and when DuckDuckGo throws up its "select all squares containing a duck" anomaly challenge, it downloads the 9 tiles, lets you pick the duck indices in your terminal, and submits the answer for you.

## Build

```sh
cargo build --release
# binary lands at ./target/release/ddg
# (the executable is `ddg`, although the crate name is `ddg-rs`)
```

## Quick start

```sh
ddg rust ownership
```

This drops you into the omniprompt after the first 10 results. Use `n` for the next page, type a result number to open it in your browser, `?` for help, or `q` to quit.

For one-shot use (no prompt), pass `--np`:

```sh
ddg --np -n 5 'rust async traits'
```

JSON output for piping:

```sh
ddg --np --json -n 5 'cloudflare turnstile' | jq '.[].url'
```

Restrict to a site, or limit by time:

```sh
ddg -w stackoverflow.com 'lifetime elision rules'
ddg -t w 'rust 1.85 release notes'      # past week
```

## CAPTCHA flow

DuckDuckGo serves a self-hosted 3×3 image grid when it doesn't trust your traffic (HTTP 202, `<form id="challenge-form">`). When `ddg` sees one it:

1. Parses the form, downloads all 9 tile JPEGs to `$TMPDIR/ddg-captcha-<ts>/tile-N.jpg`.
2. Writes a `index.html` contact sheet next to them, indexed 0..8.
3. Prints the `file://…/index.html` URL — you open it in any browser.
4. Reads your answer (e.g. `0 3 7`) from stdin, or from `<dir>/answer.txt` if stdin is not a TTY.
5. POSTs the selected `image-check_<hash>=on` boxes plus the submit token.
6. Follows the success-form redirect and returns real search results.

Up to three attempts per challenge; abort with `q` (or Ctrl-D).

```sh
# Auto-open the contact sheet in the default browser instead of just printing the URL.
ddg --open-captcha 'your query'
```

`--open-captcha` is **off by default** — it spawns a new tab per challenge, which is annoying when you're running many queries in parallel.

### Non-interactive (scripts, parallel runs)

If stdin isn't a terminal, `ddg` polls `<captcha-dir>/answer.txt` for up to 5 minutes:

```sh
ddg --np 'whatever' &
# look at the printed contact-sheet URL, identify ducks, then:
echo "0 3 7" > /tmp/.../ddg-captcha-XXXX/answer.txt
```

## Flags

| Flag | Description |
|---|---|
| `-n, --num N` | Results per page (1..=25, default 10) |
| `-r, --reg REG` | Region code (default `us-en`; `wt-wt` is no-region) |
| `-t, --time SPAN` | Time limit: `d`, `w`, `m`, `y` |
| `-w, --site SITE` | Restrict to a site (`site:` operator) |
| `-x, --expand` | Show full URLs in results |
| `-p, --proxy URI` | HTTPS proxy |
| `--unsafe` | Disable safe search |
| `--ua UA` | Custom user agent |
| `--json` | JSON output (implies `--np`) |
| `--np` | No omniprompt; print and exit |
| `--colorize {auto,always,never}` | Color mode (default `auto`) |
| `-C, --nocolor` | Alias for `--colorize=never` |
| `--open-captcha` | Auto-open the captcha contact sheet in your browser |

## Limitations

- Anomaly detection is opaque; once your IP is flagged you may keep getting challenged for a while even after solving one. Slowing down or rotating proxies (`-p`) helps.
- The image grid is the only challenge type handled. If DDG ever swaps in a Cloudflare JS gate (no `<form id="challenge-form">`), `ddg` falls back to dumping the page to a tmp file and asking you to handle it in a browser.
- Pagination supports `next` (`n`); `prev`/`first` are stubbed — re-run the query to get back to page 1.

## License

MIT — see [LICENSE](./LICENSE).
