Files
sas-bonus-watch/README.md
T
Måns SivertssonandClaude Opus 5.5 bb7a0e6a1b Colour-coded config edits, match sorting, expanded by default
- Unsaved edits: yellow = changed, green = added, red = removed on save;
  removed rows stay visible with an undo button, with a legend by Save
- Current matches: sort by departure or newest first (remembered);
  expanded by default, collapsed state remembered per watch
- Status "since" is when a flight last became bookable; tooltips spell
  out seats left vs taken

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 08:44:16 +02:00

140 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SAS bonus watch
A Tampermonkey / Violentmonkey userscript that watches SAS EuroBonus award availability and notifies you when
seats you care about appear. It runs inside your own browser, so requests to sas.se are ordinary same-origin
browser requests and it keeps working behind sas.se's Cloudflare protection.
- Any number of **watches**: origins, destinations, one travel window, cabins (Economy / Premium / Business), seats, max stops
- Finds **outbound and return** flights inside the window; optionally only alerts when both legs exist, with min/max nights
- **Gentle on sas.se**: about one request a minute, a full sweep at most every 15 minutes, backs off when refused
- Alerts via **ntfy** (phone push), **Discord**, **Slack**, any **webhook**, and desktop notifications
- Dashboard shows what is bookable now **and what you missed**: seat drops and sold-out flights
## Install
1. Install [Tampermonkey](https://www.tampermonkey.net/) (Firefox, Chrome, Edge, Safari) or
[Violentmonkey](https://violentmonkey.github.io/).
2. Open the raw script and let the extension install it:
**<https://gitea.mscloud.site/mans/sas-bonus-watch/raw/branch/main/sas-bonus-watch.user.js>**
(or: Tampermonkey → *Create a new script* → paste the file → save).
3. Open **<https://www.sas.se/robots.txt>** and keep that tab open. It turns into the dashboard.
Pin the tab so the browser doesn't unload it.
> **Chrome / Edge:** userscripts need *Developer mode* enabled on `chrome://extensions` (or "Allow user scripts"
> in the Tampermonkey extension details) since Manifest V3.
## Set up
In the **Config** section of the dashboard:
1. **Watches** – one row per search:
| Column | Meaning |
|---|---|
| On | Pause a watch without deleting it |
| From / To | Airport codes, comma-separated (`ARN, CPH`). Every From × To pair is checked |
| Earliest / Latest | Travel window. Outbound **and** return flights departing in it are found |
| Cabins | Economy, Premium (premium economy), Business |
| Seats | Seats needed **on the same flight** |
| Stops | Any, direct only, or at most 1 / 2 stops |
| Both legs | Only alert when an outbound has a later return in the window (default on). Off = alert for either direction on its own |
| Nights | Optional min / max stay, measured between the two departure dates (turns on Both legs) |
2. **Notifications** – add one or more targets:
| Type | URL |
|---|---|
| `ntfy` | `https://ntfy.sh/<long-random-topic>`. Install the ntfy app and subscribe to the same topic. Priority sets how loud it is |
| `discord` | A channel webhook URL |
| `slack` | An incoming-webhook URL |
| `webhook` | Anything; receives `POST` JSON `{title, body, watch, matches: [...]}` |
The first time the script posts to a host, Tampermonkey asks you to allow it — choose *Always allow domain*.
3. Press **Save & restart**, then **Test notification**.
4. Optional: **Baseline (no alerts)** records what is available right now, so you are only told about new seats.
### JSON export / import / reset
Under the tables, **JSON – export / import / reset** holds the same config as JSON:
*Download .json* for a backup, *Load file…* or paste + *Apply JSON to tables* to restore,
*Reset to example* to start over. Nothing takes effect until you press **Save & restart**.
Unsaved edits are colour-coded until you save or discard: **yellow** = changed field, **green** = new row,
**red** = row that will be removed on save (click ↺ to keep it).
## Dashboard
- **Current matches** per watch, sorted *By departure* or *Newest first* (most recently appeared seats on top).
Click a watch's header or summary row to collapse it; this is remembered across browser restarts:
- summary: number of flights, bookable seats per cabin with how many were lost in red (`Economy ×30 −6`), and
when the most recent flight appeared
- `Economy ×8 −2` – 8 seats left now; the red number is how many were taken since the highest count seen (10)
- red, struck through, *gone 12 okt 13:42* – sold out (or below your seat count); kept for two weeks or until departure
- grey, *no matching return* – still has seats, but its return disappeared (with Both legs)
- **Recent changes**: `+` new / back, `▲` more seats, `▼` fewer seats, `−` gone, `○` lost its return
- **Log**: every request and sweep
You are notified only when seats are **new or come back**. Drops and sold-out flights are shown, not pushed.
## How it polls
1. **Calendar** – `/bff/award-finder/destinations/v1?origin=ARN&destinations=HND&passengers=N`: one request per
route pair returns every date with seats **in both directions**. With `passengers=N` it only counts flights
that have N free seats.
2. **Details** – `/bff/award-finder/routes/v1?origin=…&destination=…&departureDate=…`: only for dates in your
window that have seats in your cabins, and only when that date's calendar entry changed (or every 6 h).
Requests are at least 40 s apart (+0–20 s random) and a sweep starts at most every 15 minutes; both are adjustable
under *Request pacing* (minimum 20 s). If sas.se refuses requests, the script backs off exponentially (up to an hour),
the badge turns red (*blocked – reload this tab*) and you get a notification. Reload the tab and complete the check
if sas.se shows one.
Only one tab polls at a time, so opening the dashboard twice does not double the rate.
## Limitations
- Runs only while the browser is open and the computer awake.
- Firefox / Chrome may unload background tabs under memory pressure; pinning the tab usually prevents it.
- Config, matches and history live in the userscript manager's storage on that browser only. Use *Download .json*
to move the config elsewhere.
## Auto-update
The script header points Tampermonkey at this repo:
```
// @version 1.3.0
// @updateURL https://gitea.mscloud.site/mans/sas-bonus-watch/raw/branch/main/sas-bonus-watch.user.js
// @downloadURL https://gitea.mscloud.site/mans/sas-bonus-watch/raw/branch/main/sas-bonus-watch.user.js
```
Tampermonkey periodically downloads `@updateURL` (default once a day; *Dashboard → Settings → Script Update*, or
*Check for userscript updates* in the extension menu to force it) and installs the new file **only if its
`@version` is higher**. Settings and history are kept across updates.
Requirements:
- **The raw URL must be readable without logging in.** Tampermonkey cannot send Gitea credentials, so the repo
(or at least this file) must be public. Check by opening the raw URL in a private window.
If the repo has to stay private, publish just the `.user.js` somewhere public (a public mirror repo, or a public
Gitea release asset) and point both URLs there.
- The script must be installed from that URL (or have the header above) for updates to be picked up.
- `@name` and `@namespace` must never change, or Tampermonkey treats it as a different script and the new copy
starts with an empty config.
### Releasing a change
1. Edit `sas-bonus-watch.user.js`.
2. Run the tests: `node --test` (Node 20+).
3. **Bump `@version`** (e.g. 1.3.0 → 1.3.1). Without this, nobody gets the update.
4. Commit and push to `main`.
## Development
- `sas-bonus-watch.user.js` – the script. The pure logic (config validation, parsing, matching, state diffing)
is exported to Node when loaded with `require`, the browser part only runs in the userscript manager.
- `sas-bonus-watch.test.cjs` – tests for the logic using response shapes captured from sas.se. Run `node --test`.
Nothing is sent anywhere except sas.se and the notification targets you configure.