Metadata-Version: 2.4
Name: pagequiet
Version: 0.1.0
Summary: Website change detection that stays quiet until something real changes.
Author: User0856
License-Expression: MIT
Project-URL: Homepage, https://github.com/User0856/pagequiet
Project-URL: Issues, https://github.com/User0856/pagequiet/issues
Keywords: website monitoring,change detection,web page diff,playwright,screenshot,visualping alternative
Classifier: Programming Language :: Python :: 3
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Topic :: Internet :: WWW/HTTP :: Site Management :: Link Checking
Classifier: Topic :: Software Development :: Testing
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: playwright
Requires-Dist: playwright>=1.40; extra == "playwright"
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Dynamic: license-file

# pagequiet

Website change detection that stays quiet until something real changes.

Most change monitors compare HTML, so they alert on every rotating ad slot, CSRF token and "updated 3 minutes ago". After a week nobody reads the alerts. `pagequiet` compares what a reader sees instead: the rendered text of one element on the page, with relative times, counters and whitespace stripped. When that changes, you get the changed lines and a full-page screenshot of the new version.

```
$ pagequiet check
CHANGED example-com-pricing https://example.com/pricing
  - Pro $49 / month
  + Pro $59 / month
  screenshot: .pagequiet/captures/example-com-pricing-20261005T080012Z.png
1 changed, 11 unchanged, 0 new, 0 errors
```

It is a single command with a TOML file, built for cron and CI: exit code 1 means something changed.

## Install

```bash
pip install "pagequiet[playwright]"
playwright install chromium
```

Python 3.11 or later. The Playwright extra renders pages with a local Chromium and costs nothing. If you would rather not run a browser (CI runners, small servers), see [Renderers](#renderers).

## Quick start

```bash
pagequiet init        # writes pagequiet.toml
# edit the [[page]] entries
pagequiet check       # first run records a baseline
pagequiet check       # later runs report changes
```

## Configuration

```toml
[settings]
renderer = "playwright"           # or "snaprender"
state = ".pagequiet/state.json"   # last text and fingerprint per page
captures = ".pagequiet/captures"  # full-page screenshot of each new version
notify = ["stdout", "slack"]      # stdout, slack, webhook
timeout = 45                      # seconds per page
use_default_noise = true          # strip relative times, timestamps, view counters

[[page]]
url = "https://example.com/pricing"
selector = "main"                          # watch one element, not the whole page
ignore = ['Offer ends in \d+ days']        # extra regex patterns removed before comparing
hide = [".testimonial-carousel"]           # hidden before text and screenshot

[[page]]
url = "https://example.com/legal/terms"
selector = "article"
screenshot = false                         # text only
```

Notifiers read their targets from the environment:

| Notifier | Variable | Sends |
|---|---|---|
| `stdout` | none | the changed lines and screenshot path |
| `slack` | `PAGEQUIET_SLACK_WEBHOOK` | a message to a Slack incoming webhook |
| `webhook` | `PAGEQUIET_WEBHOOK_URL` | JSON: `{"event": "page.changed", "name", "url", "changes", "screenshot"}` |

Exit codes: `0` nothing changed, `1` at least one page changed, `2` errors and no changes.

## What counts as a change

1. Render the page and take the visible text of `selector` (default `body`).
2. Remove noise: relative times ("5 minutes ago", "just now"), ISO timestamps and clock times, view and visitor counters, extra whitespace, plus your `ignore` patterns.
3. Hash the result. Same hash as last run: quiet. Different: report the changed lines and take a screenshot.

The defaults are deliberately few. Every pattern you strip is a kind of change you will never be told about, so add `ignore` patterns only after you have seen one cause a false alarm. If timestamps on a page are meaningful to you, set `use_default_noise = false`.

The biggest single improvement is `selector`. Watching `main` or the pricing table instead of the whole page ignores navigation, footers and "latest posts" widgets that change for reasons you do not care about.

A failed render or an empty selector is reported as an error and never as a change, and the previous text is kept, so a flaky site does not produce a false alarm the next time it loads.

## Renderers

**`playwright`** (default) runs Chromium locally through Playwright. Free, private, and fine for most pages. Cookie consent banners are part of the page, so if one varies between visits, scope `selector` to the content or add the banner to `hide`.

**`snaprender`** uses the hosted [SnapRender](https://snap-render.com) API to render pages, so no browser runs on your machine. Cookie banners and ads are removed before the text and screenshot are taken. It needs `SNAPRENDER_API_KEY`; the free plan covers 200 renders a month. Each check is one render, plus one when a page changed. Disclosure: I build SnapRender. `pagequiet` works fully without it and always will.

## Run it on a schedule

Cron, every six hours:

```bash
0 */6 * * * cd /opt/watch && pagequiet check -q >> pagequiet.log 2>&1
```

GitHub Actions, keeping state in the repository: see [`examples/github-actions.yml`](examples/github-actions.yml). Scheduled workflows run at most every five minutes, can start late at busy times, and are paused in public repositories after 60 days without activity; committing the state file each run counts as activity.

## Limits

- Pages behind a login are not supported.
- Text comparison misses purely visual changes (a new image, a broken layout). Compare the screenshots in `captures/` for those, or keep `screenshot = true` and review them when text changes.
- `hide` applies to the SnapRender renderer's screenshots only; scope its text with `selector`.

## Development

```bash
python -m venv .venv && . .venv/bin/activate
pip install -e ".[dev,playwright]"
pytest
```

MIT licensed.
