From 744434b290a080b0e777b0dc7e0de01e2b7cbe0c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 08:08:29 +0000 Subject: [PATCH 01/25] Bump python in the docker-dependencies group Bumps the docker-dependencies group with 1 update: python. Updates `python` from 3.14.1-alpine3.22 to 3.14.3-alpine3.22 --- updated-dependencies: - dependency-name: python dependency-version: 3.14.3-alpine3.22 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: docker-dependencies ... Signed-off-by: dependabot[bot] --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 1f364fd..3f0657c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 ARG webchanges_tag=v3.34.1 -FROM python:3.14.1-alpine3.22 AS builder +FROM python:3.14.3-alpine3.22 AS builder ARG webchanges_tag ENV PYTHONUTF8=1 From 234857429d9fdf1a60782a56c784fe978ce16cf1 Mon Sep 17 00:00:00 2001 From: yubiuser Date: Mon, 16 Mar 2026 10:37:39 +0100 Subject: [PATCH 02/25] Pin base images by sha to address silent rebuilds Signed-off-by: yubiuser --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 3f0657c..465d557 100644 --- a/Dockerfile +++ b/Dockerfile @@ -60,7 +60,7 @@ RUN python3 -m PyInstaller -F --strip webchanges.py -FROM alpine:3.23 AS deploy +FROM alpine:3.23.3@sha256:a76a5883dc20c193bd6eb522e940c5d3979ab4af8011d5972a928fb7156fcb9e AS deploy ENV APP_USER=webchanges ENV PYTHONUTF8=1 RUN apk add --no-cache tini From 778d59c653a82e41ae55dfd03b1e729c648d8a6e Mon Sep 17 00:00:00 2001 From: yubiuser Date: Mon, 16 Mar 2026 11:13:14 +0100 Subject: [PATCH 03/25] Use https://github.com/docker/github-builder to build and publish images Signed-off-by: yubiuser --- .github/workflows/ci.yaml | 147 +++++++++----------------------------- 1 file changed, 33 insertions(+), 114 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1d4605d..58b6220 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,123 +12,42 @@ on: env: REGISTRY_IMAGE: ghcr.io/yubiuser/webchanges +permissions: + contents: read + jobs: + build-prepare: + runs-on: ubuntu-24.04 + outputs: + REGISTRY_IMAGE: ${{ env.REGISTRY_IMAGE }} + steps: + # FIXME: can't use env object in reusable workflow inputs: https://github.com/orgs/community/discussions/26671 + - run: echo "Exposing env vars for reusable workflow" build: - runs-on: ${{ matrix.runner }} + uses: docker/github-builder/.github/workflows/build.yml@v1.2.0 permissions: contents: read - packages: write - strategy: - fail-fast: true - matrix: - include: - - platform: linux/amd64 - runner: ubuntu-latest - - platform: linux/arm64 - runner: ubuntu-24.04-arm - steps: - - - name: Prepare name for digest up/download - run: | - platform=${{ matrix.platform }} - echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - - - - name: Checkout Code - uses: actions/checkout@v6.0.2 - - - - name: Set up QEMU - uses: docker/setup-qemu-action@v4.0.0 - - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4.0.0 - - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v6 - with: - images: | - ${{ env.REGISTRY_IMAGE }} - - - name: Login to GitHub Container Registry - uses: docker/login-action@v4.0.0 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - - name: Build and push Docker image - uses: docker/build-push-action@v7.0.0 - id: build - with: - context: . - platforms: ${{ matrix.platform }} - labels: ${{ steps.meta.outputs.labels }} - provenance: false - outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=${{ github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'push' || github.event_name == 'release' }} - - - name: Export digest - run: | - mkdir -p /tmp/digests - digest="${{ steps.build.outputs.digest }}" - touch "/tmp/digests/${digest#sha256:}" - - name: Upload digest - uses: actions/upload-artifact@v7.0.0 - with: - name: digests-${{ env.PLATFORM_PAIR }} - path: /tmp/digests/* - if-no-files-found: error - retention-days: 1 - - merge: - runs-on: ubuntu-latest + packages: write # required to push to GHCR + id-token: write # for signing attestation(s) with GitHub OIDC Token needs: - - build - if: | - github.actor != 'dependabot[bot]' - && ( github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'push' || github.event_name == 'release' ) - permissions: - contents: read - packages: write - steps: - - name: Download digests - uses: actions/download-artifact@v8.0.1 - with: - path: /tmp/digests - pattern: digests-* - merge-multiple: true - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4.0.0 - - name: Docker meta - id: meta - uses: docker/metadata-action@v6 - with: - images: | - ${{ env.REGISTRY_IMAGE }} - tags: | - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}} - type=sha,enable=${{ github.event_name == 'workflow_dispatch' }} - type=ref,event=pr - type=ref,event=branch - - name: Login to GitHub Container Registry - uses: docker/login-action@v4.0.0 - with: - registry: ghcr.io + - build-prepare + with: + runner: auto + distribute: true + setup-qemu: true + output: image + push: ${{ github.actor != 'dependabot[bot]' && ( github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'push' || github.event_name == 'release' ) }} + meta-images: ${{ needs.build-prepare.outputs.REGISTRY_IMAGE }} + meta-tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=sha,enable=${{ github.event_name == 'workflow_dispatch' }} + type=ref,event=pr + type=ref,event=branch + platforms: linux/amd64,linux/arm64 + secrets: + registry-auths: | + - registry: ghcr.io username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Create manifest list and push - working-directory: /tmp/digests - run: | - docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ - $(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *) - - name: Inspect image - run: | - docker buildx imagetools inspect --raw ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }} - + password: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file From e9682abcd402c0f2bef3c2c435149e2196420266 Mon Sep 17 00:00:00 2001 From: yubiuser Date: Mon, 16 Mar 2026 11:47:14 +0100 Subject: [PATCH 04/25] Disable signing to prevent sha tag-spam in the GHCR Signed-off-by: yubiuser --- .github/workflows/ci.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 58b6220..0aa16b1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -46,6 +46,8 @@ jobs: type=ref,event=pr type=ref,event=branch platforms: linux/amd64,linux/arm64 + # FIXME: GHCR does not support the referrers API and spams the registry with sha-tagged images when cosigned: https://github.com/docker/github-builder/issues/109 + sign: false secrets: registry-auths: | - registry: ghcr.io From 7a6b4aebd06e250e9a470185ba1d51bf178ff51d Mon Sep 17 00:00:00 2001 From: yubiuser Date: Mon, 16 Mar 2026 12:02:55 +0100 Subject: [PATCH 05/25] Add caching Signed-off-by: yubiuser --- .github/workflows/ci.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0aa16b1..c187dde 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -36,6 +36,8 @@ jobs: distribute: true setup-qemu: true output: image + cache: true + cache-scope: build push: ${{ github.actor != 'dependabot[bot]' && ( github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'push' || github.event_name == 'release' ) }} meta-images: ${{ needs.build-prepare.outputs.REGISTRY_IMAGE }} meta-tags: | From d3112e37407a01c323564014c52d257ab1c2c33d Mon Sep 17 00:00:00 2001 From: yubiuser Date: Mon, 16 Mar 2026 12:08:25 +0100 Subject: [PATCH 06/25] Fix base image sha Signed-off-by: yubiuser --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 465d557..d517cd3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -60,7 +60,7 @@ RUN python3 -m PyInstaller -F --strip webchanges.py -FROM alpine:3.23.3@sha256:a76a5883dc20c193bd6eb522e940c5d3979ab4af8011d5972a928fb7156fcb9e AS deploy +FROM alpine:3.23.3@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659 AS deploy ENV APP_USER=webchanges ENV PYTHONUTF8=1 RUN apk add --no-cache tini From f0d1340a3d7736841cca0309dfce0d240eda6139 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 21 Mar 2026 16:02:26 +0000 Subject: [PATCH 07/25] Bump docker/github-builder/.github/workflows/build.yml Bumps the github_action-dependencies group with 1 update: [docker/github-builder/.github/workflows/build.yml](https://github.com/docker/github-builder). Updates `docker/github-builder/.github/workflows/build.yml` from 1.2.0 to 1.4.0 - [Release notes](https://github.com/docker/github-builder/releases) - [Commits](https://github.com/docker/github-builder/compare/v1.2.0...v1.4.0) --- updated-dependencies: - dependency-name: docker/github-builder/.github/workflows/build.yml dependency-version: 1.4.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github_action-dependencies ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c187dde..b7c297e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -24,7 +24,7 @@ jobs: # FIXME: can't use env object in reusable workflow inputs: https://github.com/orgs/community/discussions/26671 - run: echo "Exposing env vars for reusable workflow" build: - uses: docker/github-builder/.github/workflows/build.yml@v1.2.0 + uses: docker/github-builder/.github/workflows/build.yml@v1.4.0 permissions: contents: read packages: write # required to push to GHCR From e96a9a67ce38d6296d7160826d4cd613d148e26a Mon Sep 17 00:00:00 2001 From: yubiuser Date: Sun, 22 Mar 2026 09:17:45 +0100 Subject: [PATCH 08/25] Update webchanges to 3.34.2 Signed-off-by: yubiuser --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index d517cd3..9f71f55 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:1 -ARG webchanges_tag=v3.34.1 +ARG webchanges_tag=v3.34.2 FROM python:3.14.3-alpine3.22 AS builder ARG webchanges_tag From 7ec6c6ba16cd64fccac6314fe3d19c590bdcc37b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 4 Apr 2026 16:02:30 +0000 Subject: [PATCH 09/25] Bump docker/github-builder/.github/workflows/build.yml Bumps the github_action-dependencies group with 1 update: [docker/github-builder/.github/workflows/build.yml](https://github.com/docker/github-builder). Updates `docker/github-builder/.github/workflows/build.yml` from 1.4.0 to 1.5.0 - [Release notes](https://github.com/docker/github-builder/releases) - [Commits](https://github.com/docker/github-builder/compare/v1.4.0...v1.5.0) --- updated-dependencies: - dependency-name: docker/github-builder/.github/workflows/build.yml dependency-version: 1.5.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github_action-dependencies ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b7c297e..6064a05 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -24,7 +24,7 @@ jobs: # FIXME: can't use env object in reusable workflow inputs: https://github.com/orgs/community/discussions/26671 - run: echo "Exposing env vars for reusable workflow" build: - uses: docker/github-builder/.github/workflows/build.yml@v1.4.0 + uses: docker/github-builder/.github/workflows/build.yml@v1.5.0 permissions: contents: read packages: write # required to push to GHCR From a1666de9444a375ef7a4ed5cfa06bed9e98e84d7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 16:02:39 +0000 Subject: [PATCH 10/25] Bump docker/github-builder/.github/workflows/build.yml Bumps the github_action-dependencies group with 1 update: [docker/github-builder/.github/workflows/build.yml](https://github.com/docker/github-builder). Updates `docker/github-builder/.github/workflows/build.yml` from 1.5.0 to 1.6.0 - [Release notes](https://github.com/docker/github-builder/releases) - [Commits](https://github.com/docker/github-builder/compare/v1.5.0...v1.6.0) --- updated-dependencies: - dependency-name: docker/github-builder/.github/workflows/build.yml dependency-version: 1.6.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github_action-dependencies ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6064a05..3696a26 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -24,7 +24,7 @@ jobs: # FIXME: can't use env object in reusable workflow inputs: https://github.com/orgs/community/discussions/26671 - run: echo "Exposing env vars for reusable workflow" build: - uses: docker/github-builder/.github/workflows/build.yml@v1.5.0 + uses: docker/github-builder/.github/workflows/build.yml@v1.6.0 permissions: contents: read packages: write # required to push to GHCR From eed7d391c99e36548bd7ff87e9604cb53feefadb Mon Sep 17 00:00:00 2001 From: yubiuser Date: Sun, 26 Apr 2026 14:41:24 +0200 Subject: [PATCH 11/25] Update webchanges to 3.36.0 Signed-off-by: yubiuser --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 9f71f55..68cfe1f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:1 -ARG webchanges_tag=v3.34.2 +ARG webchanges_tag=v3.36.0 FROM python:3.14.3-alpine3.22 AS builder ARG webchanges_tag From f00bbe2b522ba3796846d46f72d3cab2f8ebbf1a Mon Sep 17 00:00:00 2001 From: yubiuser Date: Sun, 26 Apr 2026 14:41:38 +0200 Subject: [PATCH 12/25] Use fail-fast approach Co-authored-by: Copilot Signed-off-by: yubiuser --- .github/workflows/ci.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3696a26..2cd1283 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -38,6 +38,7 @@ jobs: output: image cache: true cache-scope: build + fail-fast: true push: ${{ github.actor != 'dependabot[bot]' && ( github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'push' || github.event_name == 'release' ) }} meta-images: ${{ needs.build-prepare.outputs.REGISTRY_IMAGE }} meta-tags: | From 17e0bdee7d8fc47f17ee1b0ad61536a80990b7d0 Mon Sep 17 00:00:00 2001 From: yubiuser Date: Sun, 26 Apr 2026 16:46:30 +0200 Subject: [PATCH 13/25] Add curl_cffi backend Co-authored-by: Copilot Signed-off-by: yubiuser --- Dockerfile | 13 +++++++------ README.md | 11 ++++++----- data/config.yaml | 7 ++++--- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/Dockerfile b/Dockerfile index 68cfe1f..87b1a36 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,17 +32,18 @@ RUN python3 -m pip install -r requirements.txt \ # Install some additional packages used by webchanges (optional) # see https://webchanges.readthedocs.io/en/stable/dependencies.html RUN python3 -m pip install \ - html5lib \ beautifulsoup4 \ - jsbeautifier \ + chump \ cssbeautifier \ + curl_cffi \ + html5lib \ jq \ - chump \ - pyopenssl \ + jsbeautifier \ minidb \ + pyopenssl \ python-dateutil \ - zstandard \ - vobject + vobject \ + zstandard # Copy entrypoint script COPY webchanges.py webchanges.py diff --git a/README.md b/README.md index f86ae17..c269863 100644 --- a/README.md +++ b/README.md @@ -6,17 +6,18 @@ The following optional dependencies of `webchanges` are included (see [Dependenc | | Comment | |---|---| -| `minidb` | to allow importing legacy `urlwatch` databases | -| `html5lib` | parser for the bs4 method of the html2text filter | | `beautifulsoup4` | `beautify` filter | -| `jsbeautifier` | `beautify` filter | +| `chump` | for `pushover` reporter | | `cssbeautifier` | `beautify` filter | +| `curl_cffi` | to allow curl_cffi backend | +| `html5lib` | parser for the bs4 method of the html2text filter | | `jq` | | -| `chump` | for `pushover` reporter | +| `jsbeautifier` | `beautify` filter | +| `minidb` | to allow importing legacy `urlwatch` databases | | `pyopenssl` | | | `python-dateutil` | for `--rollback-database` | -| `zstandard` | for Zstandard compression| | `vobject` | for iCal handling | +| `zstandard` | for Zstandard compression| ## Versioning diff --git a/data/config.yaml b/data/config.yaml index f79504d..bf2caee 100644 --- a/data/config.yaml +++ b/data/config.yaml @@ -1,6 +1,7 @@ # webchanges configuration file. See https://webchanges.readthedocs.io/en/stable/configuration.html -# Originally written by version 3.34.1. +# Originally written on 2026-04-26T12:44:12+00:00Z by version 3.36.0. +# yaml-language-server: $schema=config.schema.json display: new: true error: true @@ -126,7 +127,7 @@ report: enabled: false webhook_url: '' markdown: false - rich_text: null + rich_text: false max_message_length: null xmpp: enabled: false @@ -155,4 +156,4 @@ differ_defaults: database: engine: sqlite3 max_snapshots: 4 -footnote: null +footnote: null \ No newline at end of file From da97545c12a77825a80b74ef2c46e3cfc47fcf1f Mon Sep 17 00:00:00 2001 From: yubiuser Date: Sun, 26 Apr 2026 16:59:28 +0200 Subject: [PATCH 14/25] Add *.schema.json files Signed-off-by: yubiuser --- data/config.schema.json | 933 +++++++++++++++++++++++++++ data/jobs.schema.json | 1363 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 2296 insertions(+) create mode 100644 data/config.schema.json create mode 100644 data/jobs.schema.json diff --git a/data/config.schema.json b/data/config.schema.json new file mode 100644 index 0000000..e720c37 --- /dev/null +++ b/data/config.schema.json @@ -0,0 +1,933 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Webchanges Configuration File", + "description": "JSON Schema for the webchanges config.yaml configuration file.", + "type": "object", + "required": ["display", "report", "job_defaults", "differ_defaults", "database", "footnote"], + "properties": { + "display": { + "$ref": "#/$defs/_ConfigDisplay", + "description": "Selects which categories of jobs are included in reports (changes are always reported)." + }, + "report": { + "$ref": "#/$defs/_ConfigReport", + "description": "Settings for report formats (text, html, markdown) and reporters (stdout, email, telegram, etc.)." + }, + "job_defaults": { + "$ref": "#/$defs/_ConfigJobDefaults", + "description": "Default directives applied to jobs by kind (all, url, browser, command)." + }, + "differ_defaults": { + "$ref": "#/$defs/_ConfigDifferDefaults", + "description": "Default directives applied to individual differs." + }, + "database": { + "$ref": "#/$defs/_ConfigDatabase", + "description": "Snapshot database engine and retention settings." + }, + "footnote": { + "type": ["string", "null"], + "description": "Optional text appended to the footer of every report (text, html and markdown). Set to null to disable.", + "default": null + } + }, + "additionalProperties": false, + "$defs": { + "_ConfigDisplay": { + "title": "Display", + "description": "Which job outcomes appear in reports. Changes are always reported.", + "type": "object", + "required": ["new", "error", "unchanged", "empty-diff"], + "properties": { + "new": { + "type": "boolean", + "description": "Report newly-added jobs (jobs that have never produced a snapshot before).", + "default": true + }, + "error": { + "type": "boolean", + "description": "Report jobs that failed with an error.", + "default": true + }, + "unchanged": { + "type": "boolean", + "description": "Report jobs that ran successfully but produced no change.", + "default": false + }, + "empty-diff": { + "type": "boolean", + "description": "Deprecated. Whether to report a 'changed' job whose diff was reduced to an empty string by a diff_filter. Use the additions_only job directive instead.", + "default": false, + "deprecated": true + } + } + }, + "_ConfigReportText": { + "title": "Text report format", + "description": "Settings for the plain-text report (used by stdout, ifttt, mailgun, ntfy, prowl, pushbullet, pushover, run_command, webhook (default), xmpp).", + "type": "object", + "required": ["line_length", "details", "footer", "minimal", "separate"], + "properties": { + "line_length": { + "type": "integer", + "description": "Maximum length of each line in characters. Ignored if 'minimal' is true.", + "default": 75 + }, + "details": { + "type": "boolean", + "description": "Include the diff of each job. Ignored if 'minimal' is true.", + "default": true + }, + "footer": { + "type": "boolean", + "description": "Show a footer listing the number of jobs and elapsed time. Ignored if 'minimal' is true.", + "default": true + }, + "minimal": { + "type": "boolean", + "description": "Send an abbreviated report (suppresses details, footer, and line_length).", + "default": false + }, + "separate": { + "type": "boolean", + "description": "Send a separate report for each job instead of a single combined report.", + "default": false + } + } + }, + "_ConfigReportHtml": { + "title": "HTML report format", + "description": "Settings for the HTML report (used by browser, email with html: true).", + "type": "object", + "required": ["diff", "footer", "separate", "title"], + "properties": { + "diff": { + "enum": ["unified", "table"], + "description": "Deprecated; specify a differ in the job instead.", + "default": "unified", + "deprecated": true + }, + "footer": { + "type": "boolean", + "description": "Show a footer listing the number of jobs and elapsed time.", + "default": true + }, + "separate": { + "type": "boolean", + "description": "Send a separate report for each job instead of a single combined report.", + "default": false + }, + "title": { + "type": "string", + "description": "The HTML document title. Use {count} for the number of reports, {jobs} for the title of jobs reported, and {jobs_files} for a space followed by the name(s) of the jobs file(s) (in parentheses, with a leading 'jobs-' stripped) when not using the default jobs.yaml.", + "default": "[webchanges] {count} changes{jobs_files}: {jobs}" + } + } + }, + "_ConfigReportMarkdown": { + "title": "Markdown report format", + "description": "Settings for the Markdown report (used by gotify, matrix, telegram, webhook with markdown: true).", + "type": "object", + "required": ["details", "footer", "minimal", "separate"], + "properties": { + "details": { + "type": "boolean", + "description": "Include the diff of each job. Ignored if 'minimal' is true.", + "default": true + }, + "footer": { + "type": "boolean", + "description": "Show a footer listing the number of jobs and elapsed time. Ignored if 'minimal' is true.", + "default": true + }, + "minimal": { + "type": "boolean", + "description": "Send an abbreviated report (suppresses details and footer).", + "default": false + }, + "separate": { + "type": "boolean", + "description": "Send a separate report for each job instead of a single combined report.", + "default": false + } + } + }, + "_ConfigReportStdout": { + "title": "stdout reporter", + "description": "Display a text report on standard output (the console).", + "type": "object", + "required": ["enabled", "color"], + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the stdout reporter.", + "default": true + }, + "color": { + "anyOf": [{ "type": "boolean" }, { "enum": ["normal", "bright"] }], + "description": "Use color for additions (green) and deletions (red). true is equivalent to 'bright'; false disables color.", + "default": true + } + } + }, + "_ConfigReportBrowser": { + "title": "Browser reporter", + "description": "Open the HTML report in the system's default web browser.", + "type": "object", + "required": ["enabled"], + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the browser reporter.", + "default": false + } + } + }, + "_ConfigReportDiscord": { + "title": "Discord reporter", + "description": "Send the report as a message in a Discord channel via a webhook.", + "type": "object", + "required": ["enabled", "webhook_url", "embed", "subject", "colored", "max_message_length"], + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the Discord reporter.", + "default": false + }, + "webhook_url": { + "type": "string", + "description": "The Discord webhook URL (created via Discord server Settings → Integrations → New Webhook).", + "default": "" + }, + "embed": { + "type": "boolean", + "description": "If true, the report is sent as a Discord Embed object; the 'subject' becomes the message text and the report is shown as embedded text.", + "default": true + }, + "subject": { + "type": "string", + "description": "Only used when 'embed' is true; the message that precedes the embedded report. Use {count}, {jobs}, and {jobs_files} placeholders (see html.title for details).", + "default": "[webchanges] {count} changes{jobs_files}: {jobs}" + }, + "colored": { + "type": "boolean", + "description": "If true, embed the report as 'diff' code so Discord applies syntax highlighting (color) to additions and deletions.", + "default": true + }, + "max_message_length": { + "type": ["integer", "null"], + "description": "Maximum length of a message in characters. Defaults to Discord's limit: 2,000, or 4,096 when 'embed' is true.", + "default": null + } + } + }, + "_ConfigReportEmailSmtp": { + "title": "SMTP settings", + "description": "SMTP server settings used when email.method is 'smtp'.", + "type": "object", + "required": ["host", "user", "port", "starttls", "auth", "insecure_password", "utf_8"], + "properties": { + "host": { + "type": "string", + "description": "Address of the SMTP server.", + "default": "localhost" + }, + "user": { + "type": "string", + "description": "Username used to authenticate against the SMTP server.", + "default": "" + }, + "port": { + "type": "integer", + "description": "Port used to communicate with the SMTP server. Common values: 25 (plaintext), 465 (implicit TLS), 587 (STARTTLS).", + "default": 587 + }, + "starttls": { + "type": "boolean", + "description": "Whether the server uses STARTTLS encryption.", + "default": true + }, + "auth": { + "type": "boolean", + "description": "Whether the SMTP server requires username/password authentication.", + "default": true + }, + "insecure_password": { + "type": "string", + "description": "Password used to authenticate when not using a keyring. Storing the password in plaintext is insecure; only use a throwaway account dedicated to webchanges.", + "default": "" + }, + "utf_8": { + "type": "boolean", + "description": "Use RFC 6531 Internationalized Email (SMTPUTF8 service extension).", + "default": true + }, + "utf-8": { + "type": "boolean", + "deprecated": true, + "description": "DEPRECATED: Replace key with 'utf_8' (underscore)." + } + } + }, + "_ConfigReportEmailSendmail": { + "title": "sendmail settings", + "description": "External sendmail program settings used when email.method is 'sendmail' (Linux only).", + "type": "object", + "required": ["path"], + "properties": { + "path": { + "type": "string", + "description": "Path to (or name of) the external sendmail executable.", + "default": "sendmail" + } + } + }, + "_ConfigReportEmail": { + "title": "Email reporter", + "description": "Send the report via email, using either SMTP or the external sendmail program.", + "type": "object", + "required": ["enabled", "html", "to", "from", "subject", "method", "smtp", "sendmail"], + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the email reporter.", + "default": false + }, + "html": { + "type": "boolean", + "description": "If true, the email body includes the HTML report (in addition to the text part).", + "default": true + }, + "to": { + "type": "string", + "description": "Destination email address(es). For multiple recipients, concatenate with commas.", + "default": "" + }, + "from": { + "type": "string", + "description": "Sender's email address. Do not use a primary mailbox; create a throwaway account for webchanges.", + "default": "" + }, + "subject": { + "type": "string", + "description": "Subject line. Use {count}, {jobs}, and {jobs_files} placeholders (see html.title for details).", + "default": "[webchanges] {count} changes{jobs_files}: {jobs}" + }, + "method": { + "enum": ["sendmail", "smtp"], + "description": "Which delivery method to use.", + "default": "smtp" + }, + "smtp": { "$ref": "#/$defs/_ConfigReportEmailSmtp" }, + "sendmail": { "$ref": "#/$defs/_ConfigReportEmailSendmail" } + } + }, + "_ConfigReportGithubIssue": { + "title": "GitHub Issue reporter", + "description": "Submit the Markdown report as a new issue on a GitHub repository. Requires a personal access token with 'public_repo' (public) or 'repo' (private) scope.", + "type": "object", + "required": [ + "enabled", + "token", + "owner", + "repo", + "title", + "labels", + "format_dt", + "format_content", + "assignees", + "type", + "milestone" + ], + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the GitHub Issue reporter.", + "default": false + }, + "token": { + "type": "string", + "description": "GitHub personal access token.", + "default": "" + }, + "owner": { + "type": "string", + "description": "Owner (user or organization) of the target GitHub repository.", + "default": "" + }, + "repo": { + "type": "string", + "description": "Name of the target GitHub repository.", + "default": "" + }, + "title": { + "type": "string", + "description": "Title of the issue. Use {dt} for the date and time.", + "default": "" + }, + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Labels to add to the issue.", + "default": [] + }, + "format_dt": { + "type": "string", + "description": "strftime format for the {dt} placeholder in 'title'.", + "default": "" + }, + "format_content": { + "type": "string", + "description": "Template for the issue body. Use {content} for the report content.", + "default": "" + }, + "assignees": { + "type": "array", + "items": { "type": "string" }, + "description": "GitHub usernames to assign to the issue.", + "default": [] + }, + "type": { + "type": "string", + "description": "Type of issue to create (GitHub Enterprise feature).", + "default": "" + }, + "milestone": { + "type": "string", + "description": "Number of the milestone to associate with the issue.", + "default": "" + } + } + }, + "_ConfigReportGotify": { + "title": "Gotify reporter", + "description": "Send the Markdown report to a Gotify server. Requires an application token created on the Gotify server's Web-UI.", + "type": "object", + "required": ["enabled", "priority", "server_url", "title", "token"], + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the Gotify reporter.", + "default": false + }, + "priority": { + "type": "integer", + "description": "Gotify message priority.", + "default": 0 + }, + "server_url": { + "type": "string", + "description": "Base URL of the Gotify server.", + "default": "" + }, + "title": { + "type": "string", + "description": "Message title.", + "default": "" + }, + "token": { + "type": "string", + "description": "Gotify application token.", + "default": "" + } + } + }, + "_ConfigReportIfttt": { + "title": "IFTTT reporter", + "description": "Send the text report as an IFTTT event via the Maker Webhooks service.", + "type": "object", + "required": ["enabled", "key", "event"], + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the IFTTT reporter.", + "default": false + }, + "key": { + "type": "string", + "description": "IFTTT Maker Webhooks API key (from https://ifttt.com/maker_webhooks/settings).", + "default": "" + }, + "event": { + "type": "string", + "description": "IFTTT event name to trigger.", + "default": "" + } + } + }, + "_ConfigReportMailgun": { + "title": "Mailgun reporter", + "description": "Send the text report via email using the Mailgun service.", + "type": "object", + "required": ["enabled", "region", "api_key", "domain", "from_mail", "from_name", "to", "subject"], + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the Mailgun reporter.", + "default": false + }, + "region": { + "type": "string", + "description": "Mailgun region code (e.g. 'us', 'eu').", + "default": "us" + }, + "api_key": { + "type": "string", + "description": "Mailgun API key.", + "default": "" + }, + "domain": { + "type": "string", + "description": "Mailgun domain.", + "default": "" + }, + "from_mail": { + "type": "string", + "description": "Sender's email address.", + "default": "" + }, + "from_name": { + "type": "string", + "description": "Sender's display name.", + "default": "" + }, + "to": { + "type": "string", + "description": "Recipient's email address.", + "default": "" + }, + "subject": { + "type": "string", + "description": "Subject line. Use {count}, {jobs}, and {jobs_files} placeholders (see html.title for details).", + "default": "[webchanges] {count} changes{jobs_files}: {jobs}" + } + } + }, + "_ConfigReportMatrix": { + "title": "Matrix reporter", + "description": "Send the report to a room using the Matrix protocol.", + "type": "object", + "required": ["enabled", "homeserver", "access_token", "room_id"], + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the Matrix reporter.", + "default": false + }, + "homeserver": { + "type": "string", + "description": "Matrix homeserver URL (e.g. https://matrix.org).", + "default": "" + }, + "access_token": { + "type": "string", + "description": "Bot account access token.", + "default": "" + }, + "room_id": { + "type": "string", + "description": "Internal room ID (e.g. !roomroomroom:matrix.org).", + "default": "" + } + } + }, + "_ConfigReportNtfyPriorities": { + "title": "ntfy priorities", + "description": "Per-event-kind ntfy message priorities. Each value is either an integer 1-5 or a name ('min', 'low', 'default', 'high', 'max').", + "type": "object", + "required": ["default", "new", "changed", "error"], + "properties": { + "default": { + "type": ["integer", "string"], + "description": "Priority used when no event-specific priority applies. Integer 1-5 or one of: min, low, default, high, max.", + "default": "default" + }, + "new": { + "type": ["integer", "string"], + "description": "Priority for newly-added jobs. Integer 1-5 or one of: min, low, default, high, max.", + "default": "low" + }, + "changed": { + "type": ["integer", "string"], + "description": "Priority for changed jobs. Integer 1-5 or one of: min, low, default, high, max.", + "default": "max" + }, + "error": { + "type": ["integer", "string"], + "description": "Priority for jobs that errored. Integer 1-5 or one of: min, low, default, high, max.", + "default": "high" + } + } + }, + "_ConfigReportNtfy": { + "title": "ntfy reporter", + "description": "Send the text report to an ntfy server (https://ntfy.sh).", + "type": "object", + "required": ["enabled", "topic_url", "authorization", "priorities"], + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the ntfy reporter.", + "default": false + }, + "topic_url": { + "type": "string", + "description": "Topic URL to publish to (e.g. https://ntfy.sh/your-topic).", + "default": "" + }, + "authorization": { + "type": ["string", "null"], + "description": "Optional value for the Authorization header (see https://docs.ntfy.sh/publish/#authentication). null disables authentication.", + "default": null + }, + "priorities": { "$ref": "#/$defs/_ConfigReportNtfyPriorities" } + } + }, + "_ConfigReportProwl": { + "title": "Prowl reporter", + "description": "Send the text report via Prowl (iOS push notifications).", + "type": "object", + "required": ["enabled", "api_key", "priority", "application", "subject"], + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the Prowl reporter.", + "default": false + }, + "api_key": { + "type": "string", + "description": "Prowl API key (https://www.prowlapp.com/api_settings.php).", + "default": "" + }, + "priority": { + "type": "integer", + "description": "Prowl message priority (-2 to 2).", + "default": 0 + }, + "application": { + "type": "string", + "description": "Application name shown as the source of the event in the Prowl app.", + "default": "" + }, + "subject": { + "type": "string", + "description": "Subject line; used as the Prowl event name. Use {count}, {jobs}, and {jobs_files} placeholders (see html.title for details).", + "default": "[webchanges] {count} changes{jobs_files}: {jobs}" + } + } + }, + "_ConfigReportPushbullet": { + "title": "Pushbullet reporter", + "description": "Send the text report via Pushbullet. Note: the underlying 'pushbullet' Python package has not been maintained since 2022.", + "type": "object", + "required": ["enabled", "api_key"], + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the Pushbullet reporter.", + "default": false + }, + "api_key": { + "type": "string", + "description": "Pushbullet Access Token (https://www.pushbullet.com/#settings).", + "default": "" + } + } + }, + "_ConfigReportPushover": { + "title": "Pushover reporter", + "description": "Send the text report via Pushover (https://pushover.net).", + "type": "object", + "required": ["enabled", "app", "device", "sound", "user", "priority"], + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the Pushover reporter.", + "default": false + }, + "app": { + "type": "string", + "description": "Pushover application API token.", + "default": "" + }, + "device": { + "type": ["string", "null"], + "description": "Target device name (as listed in the Pushover console). Leave null/omit to send to all of your devices.", + "default": null + }, + "sound": { + "type": "string", + "description": "Pushover notification sound name.", + "default": "spacealarm" + }, + "user": { + "type": "string", + "description": "Pushover user key.", + "default": "" + }, + "priority": { + "type": "string", + "enum": ["lowest", "low", "normal", "high", "emergency"], + "description": "Pushover message priority. Any value other than the listed five is mapped to 'normal'.", + "default": "normal" + } + } + }, + "_ConfigReportRunCommand": { + "title": "run_command reporter", + "description": "Run a local command receiving the text report. Substitutes {count}, {jobs}, and {text} in the command string and exposes the WEBCHANGES_REPORT_CONFIG_JSON and WEBCHANGES_CHANGED_JOBS_JSON environment variables.", + "type": "object", + "required": ["enabled", "command"], + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the run_command reporter.", + "default": false + }, + "command": { + "type": "string", + "description": "The shell command to run.", + "default": "" + } + } + }, + "_ConfigReportTelegram": { + "title": "Telegram reporter", + "description": "Send the Markdown report via Telegram using the Bot API.", + "type": "object", + "required": ["enabled", "bot_token", "chat_id", "silent"], + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the Telegram reporter.", + "default": false + }, + "bot_token": { + "type": "string", + "description": "Bot authentication token from BotFather. If null/blank, the TELEGRAM_BOT_TOKEN environment variable is used instead.", + "default": "" + }, + "chat_id": { + "anyOf": [ + { "type": "string" }, + { "type": "integer" }, + { + "type": "array", + "items": { + "anyOf": [{ "type": "string" }, { "type": "integer" }] + } + } + ], + "description": "Target chat ID(s). Positive numeric IDs are private groups; negative numeric IDs are public groups; '@channelusername' targets a channel. May be a single value or a list.", + "default": "" + }, + "silent": { + "type": "boolean", + "description": "If true, deliver the notification without sound.", + "default": false + } + } + }, + "_ConfigReportWebhook": { + "title": "Webhook reporter", + "description": "Send the report to a webhook endpoint (e.g. Slack, Mattermost).", + "type": "object", + "required": ["enabled", "markdown", "webhook_url", "rich_text", "max_message_length"], + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the webhook reporter.", + "default": false + }, + "markdown": { + "type": "boolean", + "description": "If true, send the Markdown report instead of plain text.", + "default": false + }, + "webhook_url": { + "type": "string", + "description": "The webhook URL.", + "default": "" + }, + "rich_text": { + "type": "boolean", + "description": "If true, send preformatted rich text (Slack-specific).", + "default": false + }, + "max_message_length": { + "type": ["integer", "null"], + "description": "Maximum length of a message in characters. Default is 40,000.", + "default": null + } + } + }, + "_ConfigReportXmpp": { + "title": "XMPP reporter", + "description": "Send the text report via the XMPP protocol. Use a dedicated XMPP account, never a primary one.", + "type": "object", + "required": ["enabled", "sender", "recipient", "insecure_password"], + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the XMPP reporter.", + "default": false + }, + "sender": { + "type": "string", + "description": "Sender (bot) account JID.", + "default": "" + }, + "recipient": { + "type": "string", + "description": "Recipient JID.", + "default": "" + }, + "insecure_password": { + "type": ["string", "null"], + "description": "Sender password stored in plaintext. Insecure; only use a throwaway account. Ignored if a keyring entry is configured via 'webchanges --xmpp-login'.", + "default": "" + } + } + }, + "_ConfigReport": { + "title": "Report and reporter settings", + "description": "Container for report-format settings (text, html, markdown) and reporter settings.", + "type": "object", + "required": [ + "tz", + "text", + "html", + "markdown", + "stdout", + "browser", + "discord", + "email", + "github_issue", + "gotify", + "ifttt", + "mailgun", + "matrix", + "ntfy", + "prowl", + "pushbullet", + "pushover", + "run_command", + "telegram", + "webhook", + "xmpp" + ], + "properties": { + "tz": { + "type": ["string", "null"], + "description": "IANA time zone name (e.g. 'America/Los_Angeles', 'Etc/UTC') used in reports. null/blank uses the system time zone. Ignored by external differs called via the command differ.", + "default": null + }, + "text": { "$ref": "#/$defs/_ConfigReportText" }, + "html": { "$ref": "#/$defs/_ConfigReportHtml" }, + "markdown": { "$ref": "#/$defs/_ConfigReportMarkdown" }, + "stdout": { "$ref": "#/$defs/_ConfigReportStdout" }, + "browser": { "$ref": "#/$defs/_ConfigReportBrowser" }, + "discord": { "$ref": "#/$defs/_ConfigReportDiscord" }, + "email": { "$ref": "#/$defs/_ConfigReportEmail" }, + "github_issue": { "$ref": "#/$defs/_ConfigReportGithubIssue" }, + "gotify": { "$ref": "#/$defs/_ConfigReportGotify" }, + "ifttt": { "$ref": "#/$defs/_ConfigReportIfttt" }, + "mailgun": { "$ref": "#/$defs/_ConfigReportMailgun" }, + "matrix": { "$ref": "#/$defs/_ConfigReportMatrix" }, + "ntfy": { "$ref": "#/$defs/_ConfigReportNtfy" }, + "prowl": { "$ref": "#/$defs/_ConfigReportProwl" }, + "pushbullet": { "$ref": "#/$defs/_ConfigReportPushbullet" }, + "pushover": { "$ref": "#/$defs/_ConfigReportPushover" }, + "run_command": { "$ref": "#/$defs/_ConfigReportRunCommand" }, + "telegram": { "$ref": "#/$defs/_ConfigReportTelegram" }, + "webhook": { "$ref": "#/$defs/_ConfigReportWebhook" }, + "xmpp": { "$ref": "#/$defs/_ConfigReportXmpp" } + } + }, + "_ConfigJobDefaults": { + "title": "Job defaults", + "description": "Default directives applied to jobs by kind. All keys are optional; any directive set under 'url', 'browser', or 'command' overrides the same directive set under 'all' (the 'headers' directive merges header-by-header).", + "type": "object", + "properties": { + "_note": { + "type": "string", + "description": "Free-form note (ignored by webchanges; keys starting with '_' are not interpreted)." + }, + "all": { + "type": "object", + "description": "Directives applied to every job, including those defined in hooks.py." + }, + "url": { + "type": "object", + "description": "Directives applied to 'url' jobs without 'use_browser'." + }, + "browser": { + "type": "object", + "description": "Directives applied to 'url' jobs with 'use_browser: true'." + }, + "command": { + "type": "object", + "description": "Directives applied to 'command' jobs." + } + } + }, + "_ConfigDifferDefaults": { + "title": "Differ defaults", + "description": "Default directives applied to individual differs. All keys are optional.", + "type": "object", + "properties": { + "_note": { + "type": "string", + "description": "Free-form note (ignored by webchanges; keys starting with '_' are not interpreted)." + }, + "unified": { + "type": "object", + "description": "Default directives for the 'unified' differ." + }, + "ai_google": { + "type": "object", + "description": "Default directives for the 'ai_google' differ (e.g. 'model: gemini-2.0-flash')." + }, + "command": { + "type": "object", + "description": "Default directives for the 'command' differ." + }, + "deepdiff": { + "type": "object", + "description": "Default directives for the 'deepdiff' differ." + }, + "image": { + "type": "object", + "description": "Default directives for the 'image' differ." + }, + "table": { + "type": "object", + "description": "Default directives for the 'table' differ." + }, + "wdiff": { + "type": "object", + "description": "Default directives for the 'wdiff' differ." + } + } + }, + "_ConfigDatabase": { + "title": "Database", + "description": "Snapshot database engine and retention.", + "type": "object", + "required": ["engine", "max_snapshots"], + "properties": { + "engine": { + "type": "string", + "description": "Snapshot database engine. Use 'sqlite3' (default; indexed and msgpack-compressed), 'textfiles' (one text file per job; loses ETag and MIME type), 'minidb' (deprecated legacy backend), or a 'redis://' / 'rediss://' URI to use a Redis backend. Can be overridden with the --cache-engine command-line argument.", + "default": "sqlite3", + "pattern": "^(sqlite3|textfiles|minidb|rediss?://.+)$" + }, + "max_snapshots": { + "type": "integer", + "description": "Maximum number of snapshots to retain (sqlite3 engine only). 0 means retain all snapshots indefinitely. Other engines retain either all snapshots (redis, minidb) or only the latest one (textfiles). Can be overridden with the --max-snapshots command-line argument.", + "default": 4 + } + } + } + } +} diff --git a/data/jobs.schema.json b/data/jobs.schema.json new file mode 100644 index 0000000..da9a6b8 --- /dev/null +++ b/data/jobs.schema.json @@ -0,0 +1,1363 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Webchanges Jobs Configuration", + "description": "JSON Schema for each job in Webchanges' jobs.yaml configuration file", + "type": "object", + "oneOf": [{ "required": ["url"] }, { "required": ["command"] }], + "properties": { + "name": { + "type": "string", + "description": "Name of the job, used in reports." + }, + "url": { + "type": "string", + "format": "uri", + "description": "The URL to check (for URL jobs)." + }, + "command": { + "type": "string", + "description": "The shell command to execute (for Command jobs)." + }, + "note": { + "type": "string", + "description": "A note or description for the job." + }, + "user_visible_url": { + "type": "string", + "description": "The URL or text to display in reports instead of the actual checked URL." + }, + "kind": { + "type": "string", + "pattern": "^(url|browser|command|hooks_.+)$", + "description": "Explicitly specify the job kind (usually inferred)." + }, + "method": { + "type": "string", + "enum": ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"], + "default": "GET", + "description": "HTTP method to use." + }, + "data": { + "type": ["string", "object"], + "description": "Data to send in the request body (implies POST if not specified)." + }, + "data_as_json": { + "type": "boolean", + "description": "If true, sends data as JSON and sets Content-Type to application/json." + }, + "headers": { + "type": "object", + "additionalProperties": { "type": "string" }, + "description": "Custom HTTP headers." + }, + "encoding": { + "type": "string", + "description": "Override the response encoding." + }, + "timeout": { + "type": ["integer", "number"], + "description": "Request timeout in seconds." + }, + "ignore_connection_errors": { + "type": "boolean", + "description": "If true, connection errors are ignored (no report sent)." + }, + "no_redirects": { + "type": "boolean", + "description": "If true, do not follow HTTP redirects." + }, + "max_tries": { + "type": "integer", + "description": "Number of consecutive failures before reporting an error.", + "default": 1 + }, + "retries": { + "type": "integer", + "description": "Number of retries for failed requests." + }, + "cookies": { + "type": "object", + "additionalProperties": { "type": "string" }, + "description": "Cookies to send with the request." + }, + "ignore_cached": { + "type": "boolean", + "description": "If true, skips ETag/Last-Modified caching.", + "default": false + }, + "ignore_http_error_codes": { + "oneOf": [ + { "type": "integer" }, + { "type": "string" }, + { + "type": "array", + "items": { + "oneOf": [{ "type": "integer" }, { "type": "string" }] + } + } + ], + "description": "HTTP error codes to ignore (e.g. 404, '3xx', '4xx', '5xx')." + }, + "ignore_timeout_errors": { + "type": "boolean", + "description": "If true, suppresses timeout errors.", + "default": false + }, + "ignore_too_many_redirects": { + "type": "boolean", + "description": "If true, suppresses redirect loop errors.", + "default": false + }, + "no_conditional_request": { + "type": "boolean", + "description": "If true, disables conditional requests (ETag/Last-Modified).", + "default": false + }, + "http_client": { + "type": "string", + "enum": ["httpx", "requests", "curl_cffi"], + "description": "HTTP library to use.", + "default": "httpx" + }, + "impersonate": { + "type": "string", + "description": "Browser TLS fingerprint to impersonate when http_client is curl_cffi (e.g. 'chrome', 'chrome124', 'safari17_0', 'firefox133'). Defaults to 'chrome'. Ignored by other HTTP clients.", + "default": "chrome" + }, + "ignore_dh_key_too_small": { + "type": "boolean", + "description": "If true, works around weak DH key errors.", + "default": false + }, + "params": { + "type": "object", + "additionalProperties": { "type": "string" }, + "description": "Query parameters for GET/HEAD requests." + }, + "ssl_no_verify": { + "type": "boolean", + "description": "If true, skips SSL certificate validation.", + "default": false + }, + "proxy": { + "type": "string", + "description": "Proxy URL to use for the request." + }, + "use_browser": { + "oneOf": [{ "type": "boolean" }, { "type": "string", "pattern": "^(chrome|msedge|firefox$|webkit$)" }], + "description": "If true, uses a headless browser (Playwright) via the 'chrome' channel. May also be a browser name: 'firefox', 'webkit', or a value starting with 'chrome' or 'msedge' (e.g. 'chrome-beta', 'msedge-dev').", + "default": false + }, + "block_elements": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "document", + "stylesheet", + "image", + "media", + "font", + "script", + "texttrack", + "xhr", + "fetch", + "eventsource", + "websocket", + "manifest", + "other" + ] + }, + "description": "Resource types to block from loading (browser jobs)." + }, + "evaluate": { + "type": "string", + "description": "JavaScript code to execute in the page context (browser jobs)." + }, + "http_credentials": { + "type": "string", + "description": "HTTP authentication credentials ('username:password' or full URL with credentials)." + }, + "ignore_default_args": { + "oneOf": [{ "type": "boolean" }, { "type": "array", "items": { "type": "string" } }], + "description": "Override Chrome default arguments (browser jobs).", + "default": false + }, + "ignore_https_errors": { + "type": "boolean", + "description": "If true, suppresses HTTPS certificate errors (browser jobs).", + "default": false + }, + "init_script": { + "type": "string", + "description": "JavaScript code to execute before page navigation (browser jobs)." + }, + "initialization_js": { + "type": "string", + "description": "JavaScript code to execute after loading initialization_url (browser jobs)." + }, + "initialization_url": { + "type": "string", + "description": "URL to navigate to before the main URL, establishing session state (cookies). Works for all url jobs." + }, + "referer": { + "type": "string", + "description": "Referer header value (browser jobs)." + }, + "switches": { + "type": "array", + "items": { "type": "string" }, + "description": "Additional Chrome command-line switches (browser jobs)." + }, + "user_data_dir": { + "type": "string", + "description": "Path to a Chrome user data directory (for persistence/auth with use_browser)." + }, + "wait_for_function": { + "oneOf": [ + { "type": "string" }, + { + "type": "object", + "properties": { + "expression": { "type": "string" }, + "timeout": { "type": "number" }, + "polling": { "oneOf": [{ "type": "string" }, { "type": "number" }] } + } + } + ], + "description": "JavaScript expression to wait for (browser jobs)." + }, + "wait_for_selector": { + "oneOf": [ + { "type": "string" }, + { + "type": "object", + "properties": { + "selector": { "type": "string" }, + "timeout": { "type": "number" }, + "state": { "type": "string", "enum": ["attached", "detached", "visible", "hidden"] } + } + }, + { + "type": "array", + "items": { + "oneOf": [ + { "type": "string" }, + { + "type": "object", + "properties": { + "selector": { "type": "string" }, + "timeout": { "type": "number" }, + "state": { "type": "string", "enum": ["attached", "detached", "visible", "hidden"] } + } + } + ] + } + } + ], + "description": "CSS/XPath selector(s) to wait for (browser jobs)." + }, + "wait_for_timeout": { + "type": "number", + "description": "Seconds to wait after page load (browser jobs)." + }, + "wait_for_url": { + "oneOf": [ + { "type": "string" }, + { + "type": "object", + "properties": { + "url": { "type": "string" }, + "timeout": { "type": "number" } + } + } + ], + "description": "URL pattern or regex to wait for navigation to (browser jobs)." + }, + "wait_until": { + "type": "string", + "enum": ["load", "domcontentloaded", "networkidle", "commit"], + "description": "Navigation completion event to wait for (browser jobs).", + "default": "load" + }, + "filters": { + "$ref": "#/definitions/filterList", + "description": "List of filters to apply to the content." + }, + "filter": { + "$ref": "#/definitions/filterList", + "description": "Deprecated: use 'filters' instead.", + "deprecated": true, + "deprecationMessage": "Use 'filters' instead of 'filter'." + }, + "diff_filters": { + "$ref": "#/definitions/filterList", + "description": "List of filters to apply to the diff result." + }, + "diff_filter": { + "$ref": "#/definitions/filterList", + "description": "Deprecated: use 'diff_filters' instead.", + "deprecated": true, + "deprecationMessage": "Use 'diff_filters' instead of 'diff_filter'." + }, + "differ": { + "oneOf": [ + { + "type": "string", + "enum": ["unified", "ai_google", "deepdiff", "table", "wdiff"], + "description": "Differ name with default parameters." + }, + { + "type": "object", + "oneOf": [ + { + "properties": { + "name": { "type": "string", "const": "unified" }, + "context_lines": { + "type": "integer", + "description": "Number of context lines around changes.", + "default": 3 + }, + "range_info": { + "type": "boolean", + "description": "Include range information (@@ lines).", + "default": true + } + }, + "required": ["name"], + "additionalProperties": false + }, + { + "properties": { + "name": { "type": "string", "const": "ai_google" }, + "model": { + "type": "string", + "description": "Gemini model code to use.", + "default": "gemini-2.0-flash" + }, + "system_instructions": { + "type": "string", + "description": "System instructions for the AI model." + }, + "prompt": { + "type": "string", + "description": "Prompt template. Supports {old_text}, {new_text}, {unified_diff}, {unified_diff_new}." + }, + "timeout": { + "type": "number", + "description": "Request timeout in seconds.", + "default": 300 + }, + "additions_only": { + "type": "boolean", + "description": "Only process additions.", + "default": false + }, + "prompt_ud_context_lines": { + "type": "integer", + "description": "Context lines for unified diff in prompt.", + "default": 999 + }, + "temperature": { + "type": "number", + "description": "Sampling temperature (0.0–2.0).", + "default": 0.0 + }, + "thinking_budget": { + "type": "integer", + "description": "Thinking budget token count." + }, + "thinking_level": { + "type": "string", + "enum": ["low", "medium", "high"], + "description": "Thinking level." + }, + "top_k": { + "type": "integer", + "description": "Top-K sampling parameter." + }, + "top_p": { + "type": "number", + "description": "Top-P (nucleus) sampling parameter (0.0–1.0)." + }, + "tools": { + "type": "array", + "description": "Tools available to the model." + }, + "unified": { + "type": "object", + "properties": { + "context_lines": { "type": "integer", "default": 3 }, + "range_info": { "type": "boolean", "default": true } + }, + "additionalProperties": false, + "description": "Nested unified differ settings for prompt generation." + } + }, + "required": ["name"], + "additionalProperties": false + }, + { + "properties": { + "name": { "type": "string", "const": "command" }, + "command": { + "type": "string", + "description": "Command to execute for diffing." + }, + "is_html": { + "type": "boolean", + "description": "If true, the command output is HTML.", + "default": false + }, + "context_lines": { + "type": "integer", + "description": "Number of context lines around changes.", + "default": 3 + } + }, + "required": ["name", "command"], + "additionalProperties": false + }, + { + "properties": { + "name": { "type": "string", "const": "deepdiff" }, + "data_type": { + "type": "string", + "enum": ["json", "yaml", "xml"], + "description": "Data type to parse (derived from media type if not set)." + }, + "ignore_order": { + "type": "boolean", + "description": "Ignore order of elements.", + "default": false + }, + "ignore_string_case": { + "type": "boolean", + "description": "Ignore string case differences.", + "default": false + }, + "significant_digits": { + "type": "integer", + "description": "Number of significant digits for numeric comparison." + }, + "compact": { + "type": "boolean", + "description": "Use compact output format.", + "default": false + } + }, + "required": ["name"], + "additionalProperties": false + }, + { + "properties": { + "name": { "type": "string", "const": "image" }, + "data_type": { + "type": "string", + "enum": ["url", "filename", "ascii85", "base64"], + "description": "How the image data is provided.", + "default": "url" + }, + "mse_threshold": { + "type": "number", + "description": "Mean squared error threshold for detecting changes.", + "default": 2.5 + }, + "ai_google": { + "oneOf": [ + { "type": "null" }, + { "type": "boolean", "const": true }, + { + "type": "object", + "properties": { + "model": { + "type": "string", + "description": "Gemini model code to use.", + "default": "gemini-2.0-flash" + }, + "system_instructions": { + "type": "string", + "description": "System instructions for the AI model." + }, + "prompt": { + "type": "string", + "description": "Prompt template. Supports {old_text}, {new_text}, {unified_diff}, {unified_diff_new}." + }, + "timeout": { + "type": "number", + "description": "Request timeout in seconds.", + "default": 300 + }, + "additions_only": { + "type": "boolean", + "description": "Only process additions.", + "default": false + }, + "prompt_ud_context_lines": { + "type": "integer", + "description": "Context lines for unified diff in prompt.", + "default": 999 + }, + "temperature": { + "type": "number", + "description": "Sampling temperature (0.0–2.0).", + "default": 0.0 + }, + "thinking_budget": { + "type": "integer", + "description": "Thinking budget token count." + }, + "thinking_level": { + "type": "string", + "enum": ["low", "medium", "high"], + "description": "Thinking level." + }, + "top_k": { + "type": "integer", + "description": "Top-K sampling parameter." + }, + "top_p": { + "type": "number", + "description": "Top-P (nucleus) sampling parameter (0.0–1.0)." + }, + "tools": { + "type": "array", + "description": "Tools available to the model." + }, + "unified": { + "type": "object", + "properties": { + "context_lines": { "type": "integer", "default": 3 }, + "range_info": { "type": "boolean", "default": true } + }, + "additionalProperties": false, + "description": "Nested unified differ settings for prompt generation." + } + }, + "additionalProperties": false + } + ], + "description": "AI (Google Gemini) settings for image comparison." + } + }, + "required": ["name"], + "additionalProperties": false + }, + { + "properties": { + "name": { "type": "string", "const": "table" }, + "tabsize": { + "type": "integer", + "description": "Tab stop size.", + "default": 8 + } + }, + "required": ["name"], + "additionalProperties": false + }, + { + "properties": { + "name": { "type": "string", "const": "wdiff" }, + "context_lines": { + "type": "integer", + "description": "Number of context lines around changes.", + "default": 3 + }, + "range_info": { + "type": "boolean", + "description": "Include range information.", + "default": true + } + }, + "required": ["name"], + "additionalProperties": false + } + ] + } + ], + "description": "Configuration for the comparison engine." + }, + "additions_only": { + "oneOf": [ + { "type": "boolean" }, + { "type": "number", "minimum": 0, "maximum": 1 }, + { "const": "disable_safeguard" } + ], + "description": "Report only added lines. Set to true (default safeguard threshold of 0.25 — warn when 25% or less of the original content remains) or to a number in [0, 1] for the minimum fraction of original content that must remain before deletions are shown anyway. Set to \"disable_safeguard\" to keep filtering active but never show deletions even on a full wipe; set to false to disable additions-only filtering entirely." + }, + "deletions_only": { + "type": "boolean", + "description": "Report only deleted lines." + }, + "contextlines": { + "type": "integer", + "description": "Number of context lines in unified diff. Prefer setting this within the 'differ' block instead." + }, + "enabled": { + "type": "boolean", + "description": "If false, disables the job.", + "default": true + }, + "is_markdown": { + "type": "boolean", + "description": "If true, treats the data as Markdown for rendering in reports.", + "default": false + }, + "monospace": { + "type": "boolean", + "description": "If true, uses monospace font in reports.", + "default": false + }, + "suppress_error_ended": { + "type": "boolean", + "description": "If true, hides notifications when errors resolve.", + "default": false + }, + "suppress_errors": { + "type": "boolean", + "description": "If true, hides all error notifications for this job.", + "default": false + }, + "suppress_repeated_errors": { + "type": "boolean", + "description": "If true, only report the first occurrence of an error." + }, + "compared_versions": { + "type": "integer", + "description": "Number of versions to keep/compare." + }, + "hook_data": { + "description": "Any custom data to be passed to a custom job type defined in hooks.py." + } + }, + "additionalProperties": false, + "if": { + "properties": { + "kind": { "pattern": "^hooks_.+" } + }, + "required": ["kind"] + }, + "else": { + "not": { + "required": ["hook_data"] + } + }, + "definitions": { + "filterList": { + "oneOf": [ + { + "type": "string", + "description": "A single filter name (e.g. 'html2text', 'sha1sum')." + }, + { + "type": "array", + "items": { "$ref": "#/definitions/filterItem" }, + "description": "List of filters to apply in order." + }, + { + "$ref": "#/definitions/filterItemObject", + "description": "A single filter as an object." + } + ] + }, + "filterItem": { + "oneOf": [ + { + "type": "string", + "enum": [ + "absolute_links", + "ascii85", + "base64", + "beautify", + "format-json", + "format-xml", + "hexdump", + "html2text", + "ical2text", + "jsontoyaml", + "pretty-xml", + "remove_repeated", + "reverse", + "sha1sum", + "sort", + "strip" + ], + "description": "A filter name with no parameters (uses defaults)." + }, + { "$ref": "#/definitions/filterItemObject" } + ] + }, + "filterItemObject": { + "type": "object", + "minProperties": 1, + "maxProperties": 1, + "additionalProperties": false, + "patternProperties": { + "^hooks_": { + "description": "Custom hooks filter." + } + }, + "properties": { + "absolute_links": { + "type": ["boolean", "null"], + "description": "Converts relative URLs to absolute in HTML attributes.", + "default": true + }, + "ascii85": { + "type": ["boolean", "null"], + "description": "Encodes binary data using Ascii85.", + "default": true + }, + "base64": { + "type": ["boolean", "null"], + "description": "Encodes binary data using RFC 4648 Base64.", + "default": true + }, + "beautify": { + "oneOf": [ + { "type": ["boolean", "null"] }, + { + "type": "object", + "properties": { + "absolute_links": { + "type": "boolean", + "description": "Convert relative URLs to absolute.", + "default": true + }, + "indent": { + "oneOf": [{ "type": "integer" }, { "type": "string" }], + "description": "Number of spaces or string to use for indentation.", + "default": 1 + } + }, + "additionalProperties": false + } + ], + "description": "Beautifies HTML using BeautifulSoup." + }, + "css": { + "oneOf": [ + { "type": "string", "description": "CSS selector expression." }, + { + "type": "object", + "properties": { + "selector": { + "type": "string", + "description": "CSS selector expression." + }, + "method": { + "type": "string", + "enum": ["html", "xml"], + "description": "Parser method.", + "default": "html" + }, + "namespaces": { + "type": "object", + "additionalProperties": { "type": "string" }, + "description": "XML namespace mappings." + }, + "exclude": { + "type": "string", + "description": "CSS selector for elements to remove from the result." + }, + "skip": { + "type": "integer", + "description": "Number of initial matching elements to skip.", + "default": 0 + }, + "maxitems": { + "type": "integer", + "description": "Maximum number of matching elements to return." + }, + "sort": { + "type": "boolean", + "description": "Sort the matched elements.", + "default": false + } + }, + "required": ["selector"], + "additionalProperties": false + } + ], + "description": "Selects HTML/XML elements matching a CSS selector." + }, + "csv2text": { + "type": "object", + "properties": { + "format_message": { + "type": "string", + "description": "Python format string with replacement fields for each column." + }, + "has_header": { + "type": "boolean", + "description": "Whether the CSV has a header row (auto-detected if not specified)." + }, + "ignore_header": { + "type": "boolean", + "description": "If true, skip the header row in output.", + "default": false + } + }, + "required": ["format_message"], + "additionalProperties": false, + "description": "Converts CSV data to text using a format string." + }, + "delete_lines_containing": { + "oneOf": [ + { "type": "string", "description": "Literal text to match." }, + { + "type": "object", + "properties": { + "text": { + "type": "string", + "description": "Literal text to match." + }, + "re": { + "type": "string", + "description": "Regular expression pattern to match." + } + }, + "additionalProperties": false + } + ], + "description": "Removes lines containing the specified text or matching a regex pattern." + }, + "element-by-class": { + "type": "string", + "description": "Selects HTML elements matching the specified class name." + }, + "element-by-id": { + "type": "string", + "description": "Selects the HTML element with the specified id." + }, + "element-by-style": { + "type": "string", + "description": "Selects HTML elements matching the specified style attribute value." + }, + "element-by-tag": { + "type": "string", + "description": "Selects HTML elements matching the specified tag name." + }, + "execute": { + "oneOf": [ + { "type": "string", "description": "Command to execute." }, + { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "Command to execute." + }, + "escape_characters": { + "type": "boolean", + "description": "Enable Windows character escaping.", + "default": false + } + }, + "required": ["command"], + "additionalProperties": false + } + ], + "description": "Executes a command, passing data as stdin and using stdout as output." + }, + "format-json": { + "oneOf": [ + { "type": ["boolean", "null"] }, + { + "type": "object", + "properties": { + "indentation": { + "oneOf": [{ "type": "integer" }, { "type": "string" }], + "description": "Number of spaces or string for indentation.", + "default": 4 + }, + "sort_keys": { + "type": "boolean", + "description": "Sort object keys alphabetically.", + "default": false + } + }, + "additionalProperties": false + } + ], + "description": "Pretty-prints JSON data." + }, + "format-xml": { + "type": ["boolean", "null"], + "description": "Pretty-prints XML using lxml.", + "default": true + }, + "grep": { + "oneOf": [ + { "type": "string", "description": "Literal text to match." }, + { + "type": "object", + "properties": { + "text": { + "type": "string", + "description": "Literal text to match." + }, + "re": { + "type": "string", + "description": "Regular expression pattern to match." + } + }, + "additionalProperties": false + } + ], + "description": "Deprecated: use keep_lines_containing instead.", + "deprecated": true + }, + "grepi": { + "oneOf": [ + { "type": "string", "description": "Literal text to match." }, + { + "type": "object", + "properties": { + "text": { + "type": "string", + "description": "Literal text to match." + }, + "re": { + "type": "string", + "description": "Regular expression pattern to match." + } + }, + "additionalProperties": false + } + ], + "description": "Deprecated: use delete_lines_containing instead.", + "deprecated": true + }, + "hexdump": { + "type": ["boolean", "null"], + "description": "Displays data as a hex dump.", + "default": true + }, + "html2text": { + "oneOf": [ + { "type": ["boolean", "null"] }, + { + "type": "object", + "properties": { + "method": { + "type": "string", + "enum": ["html2text", "bs4", "strip_tags"], + "description": "Conversion method to use.", + "default": "html2text" + }, + "unicode_snob": { + "type": "boolean", + "description": "Use Unicode characters instead of ASCII.", + "default": true + }, + "body_width": { + "type": "integer", + "description": "Character count to wrap lines at (0 for no wrapping).", + "default": 0 + }, + "ignore_images": { + "type": "boolean", + "description": "Ignore image tags.", + "default": true + }, + "single_line_break": { + "type": "boolean", + "description": "Use single line breaks instead of double.", + "default": true + }, + "wrap_links": { + "type": "boolean", + "description": "Wrap long links.", + "default": false + }, + "pad_tables": { + "type": "boolean", + "description": "Pad table cells for alignment (html2text method)." + }, + "inline_links": { + "type": "boolean", + "description": "Use inline links instead of reference links." + }, + "protect_links": { + "type": "boolean", + "description": "Protect links from line breaks." + }, + "images_to_alt": { + "type": "boolean", + "description": "Replace images with their alt text." + }, + "images_with_size": { + "type": "boolean", + "description": "Include image size in output." + }, + "ignore_links": { + "type": "boolean", + "description": "Ignore anchor tags." + }, + "ignore_emphasis": { + "type": "boolean", + "description": "Ignore emphasis tags." + }, + "ignore_tables": { + "type": "boolean", + "description": "Ignore table structure." + }, + "decode_errors": { + "type": "string", + "description": "How to handle decode errors." + }, + "parser": { + "type": "string", + "description": "HTML parser to use (for bs4 method).", + "default": "lxml" + }, + "separator": { + "type": "string", + "description": "Separator between elements (for bs4 method).", + "default": "" + }, + "strip": { + "type": "boolean", + "description": "Strip whitespace from text (for bs4 method).", + "default": false + } + } + } + ], + "description": "Converts HTML to plain text." + }, + "ical2text": { + "type": ["boolean", "null"], + "description": "Converts iCalendar data to text.", + "default": true + }, + "jq": { + "type": "string", + "description": "Applies a jq filter expression to JSON data." + }, + "jsontoyaml": { + "oneOf": [ + { "type": ["boolean", "null"] }, + { + "type": "object", + "properties": { + "indentation": { + "oneOf": [{ "type": "integer" }, { "type": "string" }], + "description": "Number of spaces or string for indentation.", + "default": 2 + } + }, + "additionalProperties": false + } + ], + "description": "Converts JSON to YAML." + }, + "keep_lines_containing": { + "oneOf": [ + { "type": "string", "description": "Literal text to match." }, + { + "type": "object", + "properties": { + "text": { + "type": "string", + "description": "Literal text to match." + }, + "re": { + "type": "string", + "description": "Regular expression pattern to match." + } + }, + "additionalProperties": false + } + ], + "description": "Keeps only lines containing the specified text or matching a regex pattern." + }, + "ocr": { + "oneOf": [ + { "type": ["boolean", "null"] }, + { + "type": "object", + "properties": { + "timeout": { + "type": "integer", + "description": "Timeout in seconds.", + "default": 10 + }, + "language": { + "type": "string", + "description": "Language code(s) for OCR (e.g. 'eng', 'fra', 'eng+fra').", + "default": "eng" + } + }, + "additionalProperties": false + } + ], + "description": "Extracts text from images using OCR (Tesseract)." + }, + "pdf2text": { + "oneOf": [ + { "type": ["boolean", "null"] }, + { + "type": "object", + "properties": { + "password": { + "type": "string", + "description": "Password for encrypted PDFs." + }, + "physical": { + "type": "boolean", + "description": "Attempt to reproduce physical layout.", + "default": true + }, + "raw": { + "type": "boolean", + "description": "Use content stream order instead of layout.", + "default": false + } + }, + "additionalProperties": false + } + ], + "description": "Extracts text from PDF using pdftotext." + }, + "pretty-xml": { + "type": ["boolean", "null"], + "description": "Pretty-prints XML using Python xml.dom.minidom.", + "default": true + }, + "pypdf": { + "oneOf": [ + { "type": ["boolean", "null"] }, + { + "type": "object", + "properties": { + "password": { + "type": "string", + "description": "Password for encrypted PDFs." + }, + "extraction_mode": { + "type": "string", + "enum": ["layout"], + "description": "Experimental layout extraction mode." + } + }, + "additionalProperties": false + } + ], + "description": "Extracts text from PDF using pypdf." + }, + "re.findall": { + "oneOf": [ + { "type": "string", "description": "Regex pattern." }, + { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Regex pattern to search for." + }, + "repl": { + "type": "string", + "description": "Replacement string (use \\g<0> for full match, \\g<1> for groups).", + "default": "\\g<0>" + } + }, + "required": ["pattern"], + "additionalProperties": false + } + ], + "description": "Finds all regex matches and replaces content with them." + }, + "re.sub": { + "oneOf": [ + { "type": "string", "description": "Regex pattern to remove." }, + { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Regex pattern to search for." + }, + "repl": { + "type": "string", + "description": "Replacement text.", + "default": "" + } + }, + "required": ["pattern"], + "additionalProperties": false + } + ], + "description": "Replaces text matching a regex pattern." + }, + "remove_repeated": { + "oneOf": [ + { "type": ["boolean", "null"] }, + { + "type": "object", + "properties": { + "separator": { + "type": "string", + "description": "Item delimiter.", + "default": "\\n" + }, + "ignore_case": { + "type": "boolean", + "description": "Ignore case when comparing.", + "default": false + }, + "adjacent": { + "type": "boolean", + "description": "Only remove adjacent duplicates.", + "default": true + } + }, + "additionalProperties": false + } + ], + "description": "Removes duplicate items." + }, + "reverse": { + "oneOf": [ + { "type": ["boolean", "null"] }, + { + "type": "object", + "properties": { + "separator": { + "type": "string", + "description": "Item delimiter.", + "default": "\\n" + } + }, + "additionalProperties": false + } + ], + "description": "Reverses the order of items." + }, + "sha1sum": { + "type": ["boolean", "null"], + "description": "Computes the SHA-1 hash of the content.", + "default": true + }, + "shellpipe": { + "oneOf": [ + { "type": "string", "description": "Shell command to execute." }, + { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "Shell command to execute." + }, + "escape_characters": { + "type": "boolean", + "description": "Enable Windows character escaping.", + "default": false + } + }, + "required": ["command"], + "additionalProperties": false + } + ], + "description": "Pipes data through a shell command." + }, + "sort": { + "oneOf": [ + { "type": ["boolean", "null"] }, + { + "type": "object", + "properties": { + "separator": { + "type": "string", + "description": "Item delimiter.", + "default": "\\n" + }, + "reverse": { + "type": "boolean", + "description": "Sort in descending order.", + "default": false + } + }, + "additionalProperties": false + } + ], + "description": "Sorts items." + }, + "strip": { + "oneOf": [ + { "type": ["boolean", "null"] }, + { "type": "string", "description": "Characters to strip." }, + { + "type": "object", + "properties": { + "chars": { + "type": "string", + "description": "Characters to remove (default: whitespace)." + }, + "side": { + "type": "string", + "enum": ["left", "right"], + "description": "Strip from one side only." + }, + "splitlines": { + "type": "boolean", + "description": "Apply stripping to each line individually.", + "default": false + } + }, + "additionalProperties": false + } + ], + "description": "Strips leading/trailing whitespace or specified characters." + }, + "xpath": { + "oneOf": [ + { "type": "string", "description": "XPath 1.0 expression." }, + { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "XPath 1.0 expression." + }, + "method": { + "type": "string", + "enum": ["html", "xml"], + "description": "Parser method.", + "default": "html" + }, + "namespaces": { + "type": "object", + "additionalProperties": { "type": "string" }, + "description": "XML namespace mappings." + }, + "exclude": { + "type": "string", + "description": "XPath expression for elements to remove from the result." + }, + "skip": { + "type": "integer", + "description": "Number of initial matching elements to skip.", + "default": 0 + }, + "maxitems": { + "type": "integer", + "description": "Maximum number of matching elements to return." + }, + "sort": { + "type": "boolean", + "description": "Sort the matched elements.", + "default": false + } + }, + "required": ["path"], + "additionalProperties": false + } + ], + "description": "Selects HTML/XML elements matching an XPath 1.0 expression." + } + } + } + } +} From 83550a8824c919d481008eb62e5743075fdb3df5 Mon Sep 17 00:00:00 2001 From: yubiuser Date: Sun, 26 Apr 2026 17:04:29 +0200 Subject: [PATCH 15/25] Fix markdown Signed-off-by: yubiuser --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index c269863..52299f8 100644 --- a/README.md +++ b/README.md @@ -4,20 +4,20 @@ This repo provides a small docker image for running [webchanges](https://github. The following optional dependencies of `webchanges` are included (see [Dependencies](https://webchanges.readthedocs.io/en/stable/dependencies.html#dependencies)) -| | Comment | -|---|---| -| `beautifulsoup4` | `beautify` filter | -| `chump` | for `pushover` reporter | -| `cssbeautifier` | `beautify` filter | +| | Comment | +| --- | --- | +| `beautifulsoup4` | `beautify` filter | +| `chump` | for `pushover` reporter | +| `cssbeautifier` | `beautify` filter | | `curl_cffi` | to allow curl_cffi backend | -| `html5lib` | parser for the bs4 method of the html2text filter | -| `jq` | | -| `jsbeautifier` | `beautify` filter | +| `html5lib` | parser for the bs4 method of the html2text filter | +| `jq` | | +| `jsbeautifier` | `beautify` filter | | `minidb` | to allow importing legacy `urlwatch` databases | -| `pyopenssl` | | +| `pyopenssl` | | | `python-dateutil` | for `--rollback-database` | | `vobject` | for iCal handling | -| `zstandard` | for Zstandard compression| +| `zstandard` | for Zstandard compression | ## Versioning From 776f3d20b01684cd9f66b38a2f68f76166f72f82 Mon Sep 17 00:00:00 2001 From: yubiuser Date: Mon, 27 Apr 2026 07:36:51 +0200 Subject: [PATCH 16/25] Update webchanges to 3.36.1 Signed-off-by: yubiuser --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 87b1a36..61a7544 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:1 -ARG webchanges_tag=v3.36.0 +ARG webchanges_tag=v3.36.1 FROM python:3.14.3-alpine3.22 AS builder ARG webchanges_tag From 4c9cc6db6bac3d2acd8daef235ee632aeb0410a9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 9 May 2026 16:02:33 +0000 Subject: [PATCH 17/25] Bump the github_action-dependencies group with 2 updates Bumps the github_action-dependencies group with 2 updates: [docker/github-builder/.github/workflows/build.yml](https://github.com/docker/github-builder) and [Chizkiyahu/delete-untagged-ghcr-action](https://github.com/chizkiyahu/delete-untagged-ghcr-action). Updates `docker/github-builder/.github/workflows/build.yml` from 1.6.0 to 1.8.0 - [Release notes](https://github.com/docker/github-builder/releases) - [Commits](https://github.com/docker/github-builder/compare/v1.6.0...v1.8.0) Updates `Chizkiyahu/delete-untagged-ghcr-action` from 6.1.0 to 6.1.1 - [Release notes](https://github.com/chizkiyahu/delete-untagged-ghcr-action/releases) - [Commits](https://github.com/chizkiyahu/delete-untagged-ghcr-action/compare/v6.1.0...v6.1.1) --- updated-dependencies: - dependency-name: docker/github-builder/.github/workflows/build.yml dependency-version: 1.8.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github_action-dependencies - dependency-name: Chizkiyahu/delete-untagged-ghcr-action dependency-version: 6.1.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github_action-dependencies ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yaml | 2 +- .github/workflows/delete_untagged_images.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2cd1283..d02d83b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -24,7 +24,7 @@ jobs: # FIXME: can't use env object in reusable workflow inputs: https://github.com/orgs/community/discussions/26671 - run: echo "Exposing env vars for reusable workflow" build: - uses: docker/github-builder/.github/workflows/build.yml@v1.6.0 + uses: docker/github-builder/.github/workflows/build.yml@v1.8.0 permissions: contents: read packages: write # required to push to GHCR diff --git a/.github/workflows/delete_untagged_images.yml b/.github/workflows/delete_untagged_images.yml index f7e33df..a9ddb89 100644 --- a/.github/workflows/delete_untagged_images.yml +++ b/.github/workflows/delete_untagged_images.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Delete all images from repository without tags - uses: Chizkiyahu/delete-untagged-ghcr-action@v6.1.0 + uses: Chizkiyahu/delete-untagged-ghcr-action@v6.1.1 with: token: ${{ secrets.PAT_TOKEN }} repository_owner: ${{ github.repository_owner }} From 7d514f16043f1d985cc64c0d5a2e3d9ec4b4e123 Mon Sep 17 00:00:00 2001 From: yubiuser Date: Sun, 10 May 2026 09:07:04 +0200 Subject: [PATCH 18/25] Remove deprecated runner:auto Signed-off-by: yubiuser --- .github/workflows/ci.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d02d83b..17817a1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -32,7 +32,6 @@ jobs: needs: - build-prepare with: - runner: auto distribute: true setup-qemu: true output: image From dc697e225063832caca040ea4f0a3b38ad924750 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 16 May 2026 10:02:50 +0000 Subject: [PATCH 19/25] Bump the docker-dependencies group across 1 directory with 2 updates Bumps the docker-dependencies group with 2 updates in the / directory: python and alpine. Updates `python` from 3.14.3-alpine3.22 to 3.14.5-alpine3.22 Updates `alpine` from 3.23.3 to 3.23.4 --- updated-dependencies: - dependency-name: alpine dependency-version: 3.23.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: docker-dependencies - dependency-name: python dependency-version: 3.15.0a8-alpine3.22 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: docker-dependencies ... Signed-off-by: dependabot[bot] --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 61a7544..e829311 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 ARG webchanges_tag=v3.36.1 -FROM python:3.14.3-alpine3.22 AS builder +FROM python:3.14.5-alpine3.22 AS builder ARG webchanges_tag ENV PYTHONUTF8=1 @@ -61,7 +61,7 @@ RUN python3 -m PyInstaller -F --strip webchanges.py -FROM alpine:3.23.3@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659 AS deploy +FROM alpine:3.23.4@sha256:5b10f432ef3da1b8d4c7eb6c487f2f5a8f096bc91145e68878dd4a5019afde11 AS deploy ENV APP_USER=webchanges ENV PYTHONUTF8=1 RUN apk add --no-cache tini From c7d03579d73cd86ad8bddf570e15229344baeca1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 23 May 2026 16:02:28 +0000 Subject: [PATCH 20/25] Bump docker/github-builder/.github/workflows/build.yml Bumps the github_action-dependencies group with 1 update: [docker/github-builder/.github/workflows/build.yml](https://github.com/docker/github-builder). Updates `docker/github-builder/.github/workflows/build.yml` from 1.8.0 to 1.9.0 - [Release notes](https://github.com/docker/github-builder/releases) - [Commits](https://github.com/docker/github-builder/compare/v1.8.0...v1.9.0) --- updated-dependencies: - dependency-name: docker/github-builder/.github/workflows/build.yml dependency-version: 1.9.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github_action-dependencies ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 17817a1..5f3f17d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -24,7 +24,7 @@ jobs: # FIXME: can't use env object in reusable workflow inputs: https://github.com/orgs/community/discussions/26671 - run: echo "Exposing env vars for reusable workflow" build: - uses: docker/github-builder/.github/workflows/build.yml@v1.8.0 + uses: docker/github-builder/.github/workflows/build.yml@v1.9.0 permissions: contents: read packages: write # required to push to GHCR From bf32817f24346f44959395269540ee1b54cb63d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 30 May 2026 16:02:29 +0000 Subject: [PATCH 21/25] Bump docker/github-builder/.github/workflows/build.yml Bumps the github_action-dependencies group with 1 update: [docker/github-builder/.github/workflows/build.yml](https://github.com/docker/github-builder). Updates `docker/github-builder/.github/workflows/build.yml` from 1.9.0 to 1.10.0 - [Release notes](https://github.com/docker/github-builder/releases) - [Commits](https://github.com/docker/github-builder/compare/v1.9.0...v1.10.0) --- updated-dependencies: - dependency-name: docker/github-builder/.github/workflows/build.yml dependency-version: 1.10.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github_action-dependencies ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5f3f17d..7a2eeac 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -24,7 +24,7 @@ jobs: # FIXME: can't use env object in reusable workflow inputs: https://github.com/orgs/community/discussions/26671 - run: echo "Exposing env vars for reusable workflow" build: - uses: docker/github-builder/.github/workflows/build.yml@v1.9.0 + uses: docker/github-builder/.github/workflows/build.yml@v1.10.0 permissions: contents: read packages: write # required to push to GHCR From 3b5e76bc4bc1641be644faac985751430e90c065 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 6 Jun 2026 16:02:22 +0000 Subject: [PATCH 22/25] Bump the github_action-dependencies group with 2 updates Bumps the github_action-dependencies group with 2 updates: [docker/github-builder/.github/workflows/build.yml](https://github.com/docker/github-builder) and [actions/checkout](https://github.com/actions/checkout). Updates `docker/github-builder/.github/workflows/build.yml` from 1.10.0 to 1.11.0 - [Release notes](https://github.com/docker/github-builder/releases) - [Commits](https://github.com/docker/github-builder/compare/v1.10.0...v1.11.0) Updates `actions/checkout` from 6.0.2 to 6.0.3 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v6.0.2...v6.0.3) --- updated-dependencies: - dependency-name: docker/github-builder/.github/workflows/build.yml dependency-version: 1.11.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github_action-dependencies - dependency-name: actions/checkout dependency-version: 6.0.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github_action-dependencies ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yaml | 2 +- .github/workflows/sync-back-to-dev.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7a2eeac..d09acb2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -24,7 +24,7 @@ jobs: # FIXME: can't use env object in reusable workflow inputs: https://github.com/orgs/community/discussions/26671 - run: echo "Exposing env vars for reusable workflow" build: - uses: docker/github-builder/.github/workflows/build.yml@v1.10.0 + uses: docker/github-builder/.github/workflows/build.yml@v1.11.0 permissions: contents: read packages: write # required to push to GHCR diff --git a/.github/workflows/sync-back-to-dev.yml b/.github/workflows/sync-back-to-dev.yml index f791529..d097e0a 100644 --- a/.github/workflows/sync-back-to-dev.yml +++ b/.github/workflows/sync-back-to-dev.yml @@ -18,7 +18,7 @@ jobs: name: Syncing branches steps: - name: Checkout - uses: actions/checkout@v6.0.2 + uses: actions/checkout@v6.0.3 - name: Opening pull request run: gh pr create -B development -H main --title 'Sync main back into development' --body 'Created by Github action' env: From d2d3495d44a08d7f3be25c261643b49107f7c540 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 13 Jun 2026 16:02:28 +0000 Subject: [PATCH 23/25] Bump docker/github-builder/.github/workflows/build.yml Bumps the github_action-dependencies group with 1 update: [docker/github-builder/.github/workflows/build.yml](https://github.com/docker/github-builder). Updates `docker/github-builder/.github/workflows/build.yml` from 1.11.0 to 1.12.0 - [Release notes](https://github.com/docker/github-builder/releases) - [Commits](https://github.com/docker/github-builder/compare/v1.11.0...v1.12.0) --- updated-dependencies: - dependency-name: docker/github-builder/.github/workflows/build.yml dependency-version: 1.12.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github_action-dependencies ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d09acb2..8b3f859 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -24,7 +24,7 @@ jobs: # FIXME: can't use env object in reusable workflow inputs: https://github.com/orgs/community/discussions/26671 - run: echo "Exposing env vars for reusable workflow" build: - uses: docker/github-builder/.github/workflows/build.yml@v1.11.0 + uses: docker/github-builder/.github/workflows/build.yml@v1.12.0 permissions: contents: read packages: write # required to push to GHCR From 6f39d4e3a98adc1c828d3807090dc8a9b44ca2bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 20 Jun 2026 10:02:15 +0000 Subject: [PATCH 24/25] Bump alpine in the docker-dependencies group across 1 directory Bumps the docker-dependencies group with 1 update in the / directory: alpine. Updates `alpine` from 3.23.4 to 3.24.1 --- updated-dependencies: - dependency-name: alpine dependency-version: 3.24.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: docker-dependencies ... Signed-off-by: dependabot[bot] --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e829311..5d40fdd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -61,7 +61,7 @@ RUN python3 -m PyInstaller -F --strip webchanges.py -FROM alpine:3.23.4@sha256:5b10f432ef3da1b8d4c7eb6c487f2f5a8f096bc91145e68878dd4a5019afde11 AS deploy +FROM alpine:3.24.1@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b AS deploy ENV APP_USER=webchanges ENV PYTHONUTF8=1 RUN apk add --no-cache tini From 95b57856a0413feeb3de647b5d1268ef1035abb4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 20 Jun 2026 16:02:14 +0000 Subject: [PATCH 25/25] Bump actions/checkout in the github_action-dependencies group Bumps the github_action-dependencies group with 1 update: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 6.0.3 to 7.0.0 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v6.0.3...v7.0.0) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: github_action-dependencies ... Signed-off-by: dependabot[bot] --- .github/workflows/sync-back-to-dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync-back-to-dev.yml b/.github/workflows/sync-back-to-dev.yml index d097e0a..40a8458 100644 --- a/.github/workflows/sync-back-to-dev.yml +++ b/.github/workflows/sync-back-to-dev.yml @@ -18,7 +18,7 @@ jobs: name: Syncing branches steps: - name: Checkout - uses: actions/checkout@v6.0.3 + uses: actions/checkout@v7.0.0 - name: Opening pull request run: gh pr create -B development -H main --title 'Sync main back into development' --body 'Created by Github action' env: