youpdated 0.3.1__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {youpdated-0.3.1 → youpdated-0.4.0}/ADDING_SOURCES.md +28 -0
- youpdated-0.4.0/CHANGELOG.md +176 -0
- youpdated-0.4.0/CONTRIBUTING.md +121 -0
- youpdated-0.4.0/PKG-INFO +186 -0
- youpdated-0.4.0/README.md +140 -0
- {youpdated-0.3.1 → youpdated-0.4.0}/SECURITY.md +12 -7
- {youpdated-0.3.1 → youpdated-0.4.0}/pyproject.toml +6 -1
- {youpdated-0.3.1 → youpdated-0.4.0}/youpdated/__init__.py +1 -1
- {youpdated-0.3.1 → youpdated-0.4.0}/youpdated/cleanup.py +4 -3
- {youpdated-0.3.1 → youpdated-0.4.0}/youpdated/cli.py +186 -7
- {youpdated-0.3.1 → youpdated-0.4.0}/youpdated/config.py +181 -9
- {youpdated-0.3.1 → youpdated-0.4.0}/youpdated/crypto.py +7 -1
- youpdated-0.4.0/youpdated/edit.py +496 -0
- {youpdated-0.3.1 → youpdated-0.4.0}/youpdated/http.py +150 -24
- {youpdated-0.3.1 → youpdated-0.4.0}/youpdated/models.py +2 -0
- youpdated-0.4.0/youpdated/notify.py +282 -0
- {youpdated-0.3.1 → youpdated-0.4.0}/youpdated/registry.py +17 -2
- {youpdated-0.3.1 → youpdated-0.4.0}/youpdated/runner.py +23 -9
- youpdated-0.4.0/youpdated/sources/__init__.py +5 -0
- {youpdated-0.3.1 → youpdated-0.4.0}/youpdated/sources/base.py +25 -0
- {youpdated-0.3.1 → youpdated-0.4.0}/youpdated/sources/browser.py +7 -2
- {youpdated-0.3.1 → youpdated-0.4.0}/youpdated/sources/feed.py +12 -0
- {youpdated-0.3.1 → youpdated-0.4.0}/youpdated/sources/generic.py +5 -1
- youpdated-0.4.0/youpdated/sources/git.py +509 -0
- youpdated-0.4.0/youpdated/sources/homebrew.py +111 -0
- {youpdated-0.3.1 → youpdated-0.4.0}/youpdated/sources/itch.py +6 -1
- {youpdated-0.3.1 → youpdated-0.4.0}/youpdated/sources/npm.py +15 -2
- youpdated-0.4.0/youpdated/sources/pypi.py +198 -0
- {youpdated-0.3.1 → youpdated-0.4.0}/youpdated/sources/steam.py +11 -1
- {youpdated-0.3.1 → youpdated-0.4.0}/youpdated/sources/youtube.py +14 -2
- {youpdated-0.3.1 → youpdated-0.4.0}/youpdated/state.py +97 -3
- youpdated-0.4.0/youpdated.egg-info/PKG-INFO +186 -0
- {youpdated-0.3.1 → youpdated-0.4.0}/youpdated.egg-info/SOURCES.txt +5 -1
- {youpdated-0.3.1 → youpdated-0.4.0}/youpdated.egg-info/requires.txt +1 -0
- youpdated-0.3.1/CHANGELOG.md +0 -278
- youpdated-0.3.1/CONTRIBUTING.md +0 -71
- youpdated-0.3.1/PKG-INFO +0 -657
- youpdated-0.3.1/README.md +0 -612
- youpdated-0.3.1/youpdated/sources/__init__.py +0 -5
- youpdated-0.3.1/youpdated/sources/github.py +0 -180
- youpdated-0.3.1/youpdated.egg-info/PKG-INFO +0 -657
- {youpdated-0.3.1 → youpdated-0.4.0}/LICENSE +0 -0
- {youpdated-0.3.1 → youpdated-0.4.0}/MANIFEST.in +0 -0
- {youpdated-0.3.1 → youpdated-0.4.0}/setup.cfg +0 -0
- {youpdated-0.3.1 → youpdated-0.4.0}/youpdated/__main__.py +0 -0
- {youpdated-0.3.1 → youpdated-0.4.0}/youpdated/py.typed +0 -0
- {youpdated-0.3.1 → youpdated-0.4.0}/youpdated/render/__init__.py +0 -0
- {youpdated-0.3.1 → youpdated-0.4.0}/youpdated/render/json_out.py +0 -0
- {youpdated-0.3.1 → youpdated-0.4.0}/youpdated/render/rss_out.py +0 -0
- {youpdated-0.3.1 → youpdated-0.4.0}/youpdated/render/terminal.py +0 -0
- {youpdated-0.3.1 → youpdated-0.4.0}/youpdated.egg-info/dependency_links.txt +0 -0
- {youpdated-0.3.1 → youpdated-0.4.0}/youpdated.egg-info/entry_points.txt +0 -0
- {youpdated-0.3.1 → youpdated-0.4.0}/youpdated.egg-info/top_level.txt +0 -0
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
A source is one class with two methods. This guide builds a complete, working one: a `pypi` source that reports new releases of a Python package.
|
|
4
4
|
|
|
5
|
+
> Youpdated now ships a fuller `pypi` source ([youpdated/sources/pypi.py](youpdated/sources/pypi.py)).
|
|
6
|
+
> The version here is kept short to teach the contract. If you build it to follow along, give it
|
|
7
|
+
> another `name`: a plugin whose name is already taken by a built-in source is ignored.
|
|
8
|
+
|
|
5
9
|
- [The contract](#the-contract)
|
|
6
10
|
- [Two ways to add one](#two-ways-to-add-one)
|
|
7
11
|
- [Step 1: normalize config into targets](#step-1-normalize-config-into-targets)
|
|
@@ -259,6 +263,29 @@ if cached is None:
|
|
|
259
263
|
|
|
260
264
|
`client.state` may be `None`
|
|
261
265
|
|
|
266
|
+
If the key is the target's `key`, list the namespace on the source so `youpdated rm --wipe` forgets it with the rest of the target's history:
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
caches: ClassVar[tuple[str, ...]] = ("my_namespace",)
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Recognizing your entries
|
|
273
|
+
|
|
274
|
+
`youpdated add <value>` works without a source when only one source claims the value. To use, add an optional `claim()` returning the config entry for a value that can only be yours, or `None`. `accepted()` returns the entry if your `targets()` reads it, and `url_parts()` splits a URL:
|
|
275
|
+
|
|
276
|
+
```python
|
|
277
|
+
from .base import accepted, url_parts
|
|
278
|
+
|
|
279
|
+
def claim(self, value: str) -> str | None:
|
|
280
|
+
"""A project page on pypi.org"""
|
|
281
|
+
url = url_parts(value)
|
|
282
|
+
if url and url[0] == "pypi.org" and url[1].startswith("/project/"):
|
|
283
|
+
return accepted(self, url[1].split("/")[2])
|
|
284
|
+
return None
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Claim only what is distinguishable and unique: a host you own, or a shape no other source uses. A bare name like `httpx` could be several sources' entry, and two sources claiming a value stops both from getting it. Like `targets()`, `claim()` must not touch the network.
|
|
288
|
+
|
|
262
289
|
### Thread
|
|
263
290
|
|
|
264
291
|
Targets are fetched concurrently. Keep everything in local variables or on the `Target`; don't mutate shared state on the source instance.
|
|
@@ -351,4 +378,5 @@ Users then configure it like any built-in source. A plugin that fails to import
|
|
|
351
378
|
- [ ] `published` is timezone-aware UTC (or `None`)
|
|
352
379
|
- [ ] `tags` describe the update type, so `ignore:` rules can match them
|
|
353
380
|
- [ ] Tests cover parsing, both config shapes, and uid stability
|
|
381
|
+
- [ ] Optional: `claim()` for values only this source can mean, `caches` for namespaces keyed by target
|
|
354
382
|
- [ ] Registered — `@register` plus an import, or an entry point
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
4
|
+
|
|
5
|
+
## [0.4.0] — 2026-10-24
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **`youpdated add` and `youpdated rm` commands** (#37). `youpdated add git python/cpython` edits the config for you, and `rm` (`remove`) removes entries. The file is edited as text, so comments, blank lines, and quoting should be kept; the result is parsed again and nothing is written unless only the named entries changed. Entries are checked by their source first, an entry already watched in another form (a full URL for `owner/repo`) is skipped, and `rm` matches any form. Both work on an encrypted config without writing plaintext to disk, keep a symlinked config's link and a plain one's permissions, and `--test` to show the change without writing it.
|
|
10
|
+
- **The source type can be omitted for `add` and `rm`** if the entry can only mean one: `youpdated add https://github.com/neovim/neovim` goes under `git`. Package pages on npmjs.com, pypi.org, and formulae.brew.sh are written as the package name. A value no source or more than one source claims is an error that says to name one. Sources take part with an optional `claim()`; see [ADDING_SOURCES.md](ADDING_SOURCES.md#recognizing-your-entries).
|
|
11
|
+
- **`youpdated rm --wipe`** also deletes the history of what it removes: seen items, baseline, ignore-rules, names cache, cached HTTP validators of the addresses fetched, then rebuilds the database. An address another target still fetches keeps its validator
|
|
12
|
+
- **Each run records which target asked for which address** (a new `http_cache_targets` table), so `--wipe` can find a target's cached addresses. Ones cached before this are found by asking the source which addresses it fetches, without sending anything. Links go with the target's baseline when it expires.
|
|
13
|
+
- **`pypi` source** (#32). Watches a Python package's releases through PyPI's JSON Simple API, the index pip reads. Names are normalized, so `typing_extensions` and `Typing-Extensions` are considered the same. Releases whose every file is yanked are skipped; alphas, betas, release candidates, and dev releases are `prerelease`, and the highest final release is tagged `latest`. A project marked archived, deprecated, or quarantined is reported once, tagged `status` and the status.
|
|
14
|
+
- **New dependency: `packaging`** (`>=22`), the PyPA's library pip itself uses, to order and classify versions the way pip does.
|
|
15
|
+
- **`homebrew` source** (#20). Watches a formula or cask through the formulae.brew.sh JSON API. A bare name is a formula; use `cask:firefox` or `cask: firefox` for a cask. Formula versions include the revision (`1.25.0_2`), since a revision bump is a new build of the same version. The API only exposes the current version with no date, so each check reports at most one item.
|
|
16
|
+
- **A config for the tool is found from any directory** (#28). Run from a checkout, `youpdated.yaml` or `youpdated.yml` at its root is now used when the current directory has none, ahead of the per-user config. `uninstall` lists it with the other project configs.
|
|
17
|
+
- **`git` source for GitHub, GitLab, and Forgejo/Gitea** (#33, #34, #35). One entry for any forge: a repo URL, `host/owner/repo`, or `owner/repo` for GitHub. Watches releases, tags, and commits. The forge is known for github.com, gitlab.com, codeberg.org, and gitea.com, guessed from a self-hosted hostname, or set with `forge:`. GitLab subgroups and GitHub Enterprise function. Tokens are optional and read from the environment (`GITHUB_TOKEN`, `GITLAB_TOKEN`, or per-entry `token_env:`).
|
|
18
|
+
- **Notifications** (#36). A `notify:` block pushes each run's new updates to webhooks (ntfy, Discord, Slack, or plain JSON, with the format guessed from URL) and/or a command run once per update. A command is never run through a shell: update fields are passed as `{placeholder}` arguments, `YOUPDATED_*` environment variables, and JSON on stdin. `--notify-cmd` adds a command from the command line, and `--no-notify`/`--notify` force sending off or on. Baselines, `--all`, and `--no-save`, by default, send nothing. Failures show in the problems panel and count for `--fail-on-error`, with webhook URLs reduced to their host.
|
|
19
|
+
- **A weekly canary against live services.** The test suite runs on recorded fixtures, so it can't check if a source changes anything important. The `canary` workflow runs every source against a real target (`.github/canary/canary.yaml`), fails a source that errors or parses nothing, and keeps a `canary` issue open until a later run passes.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- **Breaking: the `github` source is now part of `git`** (#35). GitHub updates are reported with source `git`, and their target includes the host: `github.com/python/cpython` instead of `python/cpython`. This shows in the terminal, `--json`, `--rss`, and notifications. Scripts that match `.source == "github"` or a bare `owner/repo` target will match nothing, with no error. Match `.target | startswith("github.com/")` instead.
|
|
24
|
+
|
|
25
|
+
Configs should continue to work: `github:` is another name for `git`, `-s github` selects `git`, and `ignore: {github: [...]}` still covers the entries listed under `github:`. History is moved to the new names the first time it is opened, so nothing is reported again. A repo listed under both `github:` and `git:` reports each update once. (Reach out if any errors occur to the GitHub)
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- **GitHub releases no longer re-report when `GITHUB_TOKEN` is set or unset** (#24). The atom feed keyed releases by entry id and the REST API by release id, so every switch reported each release again. Both now key them by tag. History recorded under the old ids still counts as seen.
|
|
30
|
+
- **Tokens and webhook headers no longer follow redirects to another host.** httpx strips `Authorization` when a redirect leaves the origin, but nothing else, so a GitLab server answering with a redirect elsewhere was able to carry `PRIVATE-TOKEN`, same for a webhook's custom auth header. Redirects are now followed by Youpdated, and every header a source or webhook supplies is dropped on the first hop to a different origin (keeping only `User-Agent`, `Accept`, and `Accept-Language`). Same-host redirects and http to https upgrades keep headers.
|
|
31
|
+
- **An encrypted file can't ask for an unbounded key derivation.** The scrypt cost is read from the header before the file can be authenticated, so a crafted file could request hundreds of GiB of memory. Costs above 16× the default (about 1 GiB) are refused before scrypt runs. Every file Youpdated has written uses the default and is unaffected. This was only potentially an issue if a malicious actor was able to create a youpdated config on a machine, at which point, security is mostly lost anyways, but is still now fixed.
|
|
32
|
+
|
|
33
|
+
## [0.3.1] — 2026-10-10
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- **itch builds no longer re-report on unchanged pages.** itch.io shuffles HTML attribute order, and the upload list was only read in one order, so some fetches saw no files and fingerprinted a "new build". Successive runs each reported a different handful of games until every variant had been seen. Upload and timestamp parsing no longer depend on attribute order.
|
|
38
|
+
- **YouTube Shorts no longer re-report when the feed falls back.** The official feed links Shorts as `/shorts/<id>`, which was not recognised, so Shorts were recorded as `yt:video:yt:video:<id>` there and `yt:video:<id>` through Invidious or Data API. Each switch reported Shorts again. Existing history is migrated on first open, so nothing already seen comes back.
|
|
39
|
+
- **`--no-save` really leaves the state alone.** It still stored each feed's ETag/Last-Modified, so the next real run was answered 304 and silently skipped whatever the preview had shown. (Whoops)
|
|
40
|
+
- **A target that fails partway no longer hides its new items.** If one of a target's fetches failed (e.g. GitHub `watch: [releases, commits]` with commits down), the others' validators were still stored while their items were discarded, so the next run got a 304 and never reported them. Validators are now stored only alongside the items they cover.
|
|
41
|
+
- **Feeds listed oldest-first report new entries.** Entries were cut to the limit in document order before sorting, so on an oldest-first feed longer than the limit the newest posts never made it in. Affects `feed`, Steam, itch devlogs, and GitHub atom feeds.
|
|
42
|
+
- **Chrome no longer errors** when a version is first listed without a rollout start time and later with.
|
|
43
|
+
- **Removing an `ignore:` rule shows what it hid.** Ignored items are not recorded, but the feed's validators were, so hidden items stayed hidden until it next changed. Each target's rules are now recorded, and a target whose rules changed is fetched fully once. Every target is fetched in full once after upgrading.
|
|
44
|
+
|
|
45
|
+
## [0.3.0] — 2026-10-07
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
- **`ignore:` rules drop update types** (#11). Every update already carried tags describing what it is; those are now filterable. A bare list applies everywhere, a mapping narrows it to one source (`'*'` for all), and a per-entry `ignore:` adds to whichever applies:
|
|
50
|
+
|
|
51
|
+
```yaml
|
|
52
|
+
ignore:
|
|
53
|
+
'*': [prerelease]
|
|
54
|
+
github: [commit]
|
|
55
|
+
|
|
56
|
+
sources:
|
|
57
|
+
github:
|
|
58
|
+
- repo: astral-sh/uv
|
|
59
|
+
ignore: [tag] # global rules + `tag`
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
An update is dropped if it carries any listed tag. Ignored items are not recorded as seen, so removing a rule later shows what it was hiding. The run summary and `--json` both count what was dropped. See [Ignoring update types](docs/configuration.md#ignoring-update-types) for the tag vocabulary per source.
|
|
63
|
+
|
|
64
|
+
- **GitHub prereleases are tagged without a token.** `prerelease` previously came only from the REST API, which needs `GITHUB_TOKEN`, so on the anonymous `.atom` path nothing was tagged. It is now inferred from the tag name (`v3.15.0rc2`, `1.2.0-beta.1`, `0.12.0-alpha`), and a token takes precedence. Platform and build suffixes (`v1.0.0-linux`, `v4.2.0+build.7`) are untouched.
|
|
65
|
+
|
|
66
|
+
- **npm prereleases are tagged.** Any semver version with a prerelease suffix now carries `prerelease`. Build metadata (`+build-7`) does not count, even with a hyphen in it.
|
|
67
|
+
|
|
68
|
+
- **Runs report progress instead of going quiet.** A check printed nothing until the last target landed, so a slow source was indistinguishable from a hang. Interactive runs now show a live line naming the targets still in flight, with a count and elapsed time. It is suppressed under `-v`, `--test`, and when output is not a terminal.
|
|
69
|
+
|
|
70
|
+
- **A desktop example app** (#13), in `examples/desktop/`. A simple window checks on a schedule, sends a notification, and lists what changed. Uses standard library and youpdated. It reads the same config and history as the CLI, and prompts for a passphrase when either is encrypted. Its own CI covers Linux, macOS, and Windows, and runs only when `examples/` changes
|
|
71
|
+
|
|
72
|
+
### Changed
|
|
73
|
+
|
|
74
|
+
- **Install docs now with `pipx install youpdated`.** The package has been on PyPI since 0.1.0, but the README still told people to clone the repo and install from the working tree. Installing from source is now a subsection. The scheduling examples point at a `pipx` path rather than a repo virtualenv.
|
|
75
|
+
- **itch builds are tagged `build`, and page-only changes `page`,** instead of both being `release`. The tags say what changed, so `ignore:` can tell them apart. Anything filtering `--json` or `--rss` output on `release` for itch should use `build`.
|
|
76
|
+
|
|
77
|
+
### Fixed
|
|
78
|
+
|
|
79
|
+
- **A host's pacing lock is no longer held across its own wait.** `_pace` slept while holding the per-host lock, so every other thread bound for that host blocked in `acquire` for the whole gap, with no way to be interrupted, and a thread waiting its turn could be scheduled ahead of one ready to do real work. Each caller now reserves a slot under the lock and waits outside it. The spacing between requests to one host is unchanged.
|
|
80
|
+
- **Progress bar moved around** (#14). The target names led the line, so the bar and counter shifted sideways every time they changed, and a long name squeezed the bar out. The spinner, bar, count, elapsed time and failure count now come first at a fixed width, and the names fill what is left of the line, truncated with an ellipsis. Names are shown literally, so one containing brackets (`[/x]`) no longer crashes the bar.
|
|
81
|
+
- **Rate-limit retries honor `Retry-After`** (#17). A 429 or 503 was retried on a fixed rate ignoring any potential 'Retry-After' sent. The wait now follows the header (seconds or an HTTP-date) and holds the whole host, so other targets on that site wait too. A server asking for more than 60s fails that request at once instead of stalling the run. Without the header, the fixed backoff applies as before.
|
|
82
|
+
- **The history database is pruned** (#18). Seen items were added, so the database grew, including for targets removed from the config, and with encryption, the whole thing is decrypted and re-encrypted each run. An item missing from every fetch for longer than the new top-level `expiry:` (default `1y`, `never` to keep everything) is now forgotten, and the file is compacted. Items still listed by their source are never pruned, and stored validators older than half the expiry are skipped once to force a full fetch. Targets that failed or were left out with `--source` keep their history. A removed target's baseline expires with its history, so re-adding it later records a new baseline instead of reporting everything it has. `--json` counts what was pruned, and `--since` now also accepts years (`1y`).
|
|
83
|
+
- **A target added later no longer dumps a backlog.** The first run baseline applied to the whole state, so after it, a newly added target reported every item it had as new. Same for a target that failed the first run, or `-s`. Baselines are now recorded per target on its first successful fetch, and the run names those targets. State from earlier versions is read as already baselined, so nothing is swallowed on upgrade. A target's first fetch sends no stored validators, so a new channel on a document another target already watches (Brave, Firefox, Edge) records a real baseline instead of a 304.
|
|
84
|
+
- **`--test` lines read `[test] GET`**, not `[test]] GET`.
|
|
85
|
+
|
|
86
|
+
## [0.2.1] — 2026-08-25
|
|
87
|
+
|
|
88
|
+
### Fixed
|
|
89
|
+
|
|
90
|
+
- **The release workflow's `attach` job failed on v0.2.0 and has been removed.** It copied the built artifacts onto the GitHub Release, but this repo has immutable releases enabled, which freezes assets when the release is published — and the job runs after that, so it could only ever fail (`HTTP 422: Cannot upload assets to an immutable release`). Publishing to PyPI was unaffected; 0.2.0 shipped correctly. Releases from now on will not carry attached `.whl`/`.tar.gz` files. Get them from PyPI, where they are covered by a signed PEP 740 attestation binding each digest to this repository and workflow — a stronger guarantee than the copy the job was making.
|
|
91
|
+
|
|
92
|
+
- **Targets sharing one upstream document lost their updates** Every Brave channel is served by one GitHub releases document, and every Firefox channel by one Mozilla JSON. Each channel fetched it separately, so the first stored an ETag and the rest were answered `304 Not Modified` against that ETag moments later and reported *nothing*. With the default `jitter`, per-host pacing serializes those requests, which is the case that loses: a config watching five Firefox channels and three Brave channels reported 2 of 8. Under concurrency it was non-deterministic: is a channel reported depended on thread timing, so a loaded machine changed the result.
|
|
93
|
+
|
|
94
|
+
A run now reuses each document across the targets that share it (`Client.run_scope()`), so the document is fetched once and every target sees it. The same config went from 8 requests reporting 2 of 8 targets, to 2 requests reporting 8 of 8. (And 147 ms to 43 ms of wall time) Outside a run the client is unchanged: one GET per call, and a 304 still means "nothing new".
|
|
95
|
+
|
|
96
|
+
- **The YouTube official-feed path never set the channel label.** `_label_and_parse` did not do the labelling it was supposed to, so a channel that answered on the primary path was reported by its raw `@handle` while the Invidious and Data API fallbacks named it properly. The label now comes from the feed on all three paths.
|
|
97
|
+
|
|
98
|
+
- **`feed` sources parsed every feed twice** A `feed:` entry given as a bare URL had no label, so `FeedSource.fetch` parsed the document once to read the feed's own `<title>` and then handed the same bytes to `parse_feed`, which parsed them again. Feed parsing is the most expensive part of a run, so this ~doubled the CPU cost of every bare-URL feed. One parse now feeds both the label and the entries.
|
|
99
|
+
|
|
100
|
+
`parse_feed()` is unchanged for callers that only need entries; it is now a wrapper over the new `parse_document()` / `parse_entries()` split in `youpdated.sources.feed`.
|
|
101
|
+
|
|
102
|
+
## [0.2.0] — 2026-08-19
|
|
103
|
+
|
|
104
|
+
### Added
|
|
105
|
+
|
|
106
|
+
- **Encryption at rest for the config and history** ([#5](https://github.com/Void1-1/youpdated/issues/5)). `youpdated encrypt` (alias `set-encrypted`) converts an existing setup in place; `youpdated decrypt` converts it back. Every command detects an encrypted setup on its own and asks for the passphrase once, or reads it from `YOUPDATED_PASSPHRASE` for unattended runs.
|
|
107
|
+
|
|
108
|
+
Files are encrypted whole with AES-256-GCM under a scrypt-derived key (n=2¹⁶, r=8, p=1), with the KDF parameters authenticated as additional data so they cannot be downgraded. Decryption happens **in memory**: the config is parsed from a decrypted buffer and the SQLite database is deserialized into an in-memory database and written back encrypted when the run ends, so no plaintext copy is put on disk even mid-run. A read-only run leaves the file byte-for-byte alone.
|
|
109
|
+
|
|
110
|
+
Needs the `cryptography` package: `pip install 'youpdated[encryption]'`. Installs without it behave exactly as before.
|
|
111
|
+
|
|
112
|
+
- **`youpdated init --encrypt`** writes the starter config already encrypted, so a setup that is meant to be private never has a plaintext config on disk at all — unlike `init` then `encrypt`, which leaves the original blocks in free space.
|
|
113
|
+
|
|
114
|
+
- **A proxy preflight.** When `privacy.proxy` is set, the proxy is checked before the run starts and the run is refused with exit `1` if it is unreachable. `--test` reports it instead of aborting.
|
|
115
|
+
|
|
116
|
+
- **`youpdated.crypto` is a documented standalone module.** `from youpdated import crypto` gives you the container directly; everything in its `__all__` is a supported surface. It stays an optional *dependency* rather than a separate distribution on purpose: nothing in it imports `cryptography` at module scope, so a plain install already pays nothing for it, and shipping the container format apart from the code that reads it would risk version skew on files that are the user's only copy.
|
|
117
|
+
|
|
118
|
+
### Changed
|
|
119
|
+
|
|
120
|
+
- **A dead proxy now stops the run instead of failing every target.** Requests already failed closed (httpx routes everything through the proxy and never falls back to a direct connection) but with Tor off, a run would fail each target separately, record an empty baseline, and still exit `0`. In a cron log that is indistinguishable from "nothing new". It now exits `1` before the state database is even opened, so nothing is recorded.
|
|
121
|
+
|
|
122
|
+
### Fixed
|
|
123
|
+
|
|
124
|
+
- **A broken SOCKS handshake escaped the retry path.** `socksio` raises `ProtocolError`, which is not an `httpx.HTTPError`, so a proxy port answering with something that is not SOCKS5 bypassed the retries and surfaced as a raw exception rather than a `FetchError`. The client now treats `SOCKSError` as a network error like any other.
|
|
125
|
+
|
|
126
|
+
- **`tests/test_cleanup.py` could delete a real `./youpdated.yaml`.** The fixture redirected the config and data directories but not the working directory, so `find_traces()` picked up the project config of whoever ran the suite from a directory that had one, and `remove_traces()` deleted it. The fixture now chdirs to the temp directory.
|
|
127
|
+
|
|
128
|
+
## [0.1.1] — 2026-08-18
|
|
129
|
+
|
|
130
|
+
### Fixed
|
|
131
|
+
|
|
132
|
+
- **`browser` / Brave: a server error killed the target instead of falling back.** The Brave source reads the GitHub REST API and keeps the `.atom` feed as a fallback, but only a 403 or 429 reached it. A 5xx: a timeout, or a DNS failure, was retried, then raised, and the whole target was reported as failed. Observed against `api.github.com` returning 504. Any failure the HTTP client gives up on now falls back to the atom feed; a genuine outage of *both* still reports an error.
|
|
133
|
+
|
|
134
|
+
### Changed
|
|
135
|
+
|
|
136
|
+
- Retry backoff is configurable on the HTTP client (`retry_backoff`), so the test suite no longer spends real seconds exercising retry paths. The suite went from ~4.9s to ~0.4s.
|
|
137
|
+
|
|
138
|
+
### Added
|
|
139
|
+
|
|
140
|
+
- A `release` workflow that publishes to PyPI via trusted publishing when a GitHub Release is published, gated on the full 13-job test matrix and on the tag matching the version in `pyproject.toml`.
|
|
141
|
+
|
|
142
|
+
## [0.1.0] — 2026-08-18
|
|
143
|
+
|
|
144
|
+
First release.
|
|
145
|
+
|
|
146
|
+
### Added
|
|
147
|
+
|
|
148
|
+
- **Seven sources**, all working without accounts or API keys:
|
|
149
|
+
- `github` — releases, tags, and commits via `.atom` feeds
|
|
150
|
+
- `npm` — newly published versions from public registry
|
|
151
|
+
- `steam` — patch notes and news; resolves the store name from a bare appid
|
|
152
|
+
- `itch` — devlog posts and new builds, fingerprinted from the game page
|
|
153
|
+
- `youtube` — channels and playlists, with Invidious and Data API fallback
|
|
154
|
+
- `browser` — Chrome, Brave, Firefox, and Edge releases across platforms and channels
|
|
155
|
+
- `feed` — any RSS/Atom URL, for apps without a dedicated source
|
|
156
|
+
- **Plugin architecture**: sources register in-tree with `@register` or ship from a third-party package through a `youpdated.sources` entry point.
|
|
157
|
+
- **Config**: every source entry takes a bare value for the common case or a mapping for advanced options
|
|
158
|
+
- **Incremental reporting**: a SQLite history so each run reports what changed. First run records a baseline.
|
|
159
|
+
- **Three output formats**: terminal report, `--json`, and `--rss`
|
|
160
|
+
- **Privacy controls**: optional SOCKS/HTTP proxy covering every request, user-agent rotation, per-host request pacing with jitter, per-request cookie clearing, and conditional GETs. `--test` prints URLs without sending
|
|
161
|
+
- **`youpdated uninstall`** to remove every file from the tool. Refuses directories it didn't create or have other files.
|
|
162
|
+
|
|
163
|
+
### Known issues
|
|
164
|
+
|
|
165
|
+
- YouTube's RSS endpoint throttles occassionaly and 404s valid URLs; fallback covers, but a run can still fail all three. Retry or set `privacy.proxy`.
|
|
166
|
+
- Some itch games publish no "Updated" timestamp, so build updates are reported undated.
|
|
167
|
+
- Firefox publishes current versions, so it reports one item per channel.
|
|
168
|
+
- Edge exposes release notes only for the stable and beta channels. (But like, it's Edge, why do you want to know when it updates?)
|
|
169
|
+
|
|
170
|
+
[0.4.0]: https://github.com/Void1-1/youpdated/releases/tag/v0.4.0
|
|
171
|
+
[0.3.1]: https://github.com/Void1-1/youpdated/releases/tag/v0.3.1
|
|
172
|
+
[0.3.0]: https://github.com/Void1-1/youpdated/releases/tag/v0.3.0
|
|
173
|
+
[0.2.1]: https://github.com/Void1-1/youpdated/releases/tag/v0.2.1
|
|
174
|
+
[0.2.0]: https://github.com/Void1-1/youpdated/releases/tag/v0.2.0
|
|
175
|
+
[0.1.1]: https://github.com/Void1-1/youpdated/releases/tag/v0.1.1
|
|
176
|
+
[0.1.0]: https://github.com/Void1-1/youpdated/releases/tag/v0.1.0
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for looking. This is a small project with a few constraints.
|
|
4
|
+
|
|
5
|
+
## The constraints
|
|
6
|
+
|
|
7
|
+
These are the reasons the tool exists, so a change that breaks one needs a strong argument:
|
|
8
|
+
|
|
9
|
+
1. **No accounts, no required API keys.** Every source works unauthenticated. Keys are read from the environment when present, only to raise rate limits, and are never written to disk.
|
|
10
|
+
2. **Nothing leaves the machine** except requests to the sources the user configured. No telemetry, no crash reporting, no update checks for Youpdated itself.
|
|
11
|
+
3. **One HTTP path.** Every request goes through `client.get()` in [youpdated/http.py](youpdated/http.py).
|
|
12
|
+
4. **Files stay in the platform's config and data directories.** Nowhere else.
|
|
13
|
+
|
|
14
|
+
## Setting up
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
git clone https://github.com/Void1-1/youpdated
|
|
18
|
+
cd youpdated
|
|
19
|
+
python3 -m venv .venv
|
|
20
|
+
.venv/bin/pip install ".[dev]"
|
|
21
|
+
PYTHONPATH=$PWD .venv/bin/python -m pytest
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`PYTHONPATH=$PWD` makes your working tree take effect without reinstalling. On Windows: `$env:PYTHONPATH=$PWD; .venv\Scripts\python -m pytest`.
|
|
25
|
+
|
|
26
|
+
Run the CLI from your working tree:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
PYTHONPATH=$PWD .venv/bin/python -m youpdated check --test -v
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The install also puts a `youpdated` command at `.venv/bin/youpdated` on macOS/Linux and `.venv\Scripts\youpdated.exe` on Windows. It runs the code as of the last install, so reinstall with `.venv/bin/pip install .` after changing source code. Call it by that full path, or put the directory on your `PATH`:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
export PATH="$PWD/.venv/bin:$PATH" # macOS / Linux
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```powershell
|
|
39
|
+
$env:PATH = "$PWD\.venv\Scripts;$env:PATH" # Windows
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
A `youpdated.yaml` at the root of the checkout is picked up from any directory, so a development config can live there.
|
|
43
|
+
|
|
44
|
+
## Sending a change
|
|
45
|
+
|
|
46
|
+
`main` requires a pull request with passing CI:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
git switch -c my-change
|
|
50
|
+
# work, commit
|
|
51
|
+
git push -u origin my-change
|
|
52
|
+
gh pr create --fill
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
CI runs the suite on Linux, macOS, and Windows across Python 3.11–3.14. All 13 jobs must be green, and the branch must be up to date with `main`. No review approvals are required.
|
|
56
|
+
|
|
57
|
+
## Tests
|
|
58
|
+
|
|
59
|
+
The suite is **fully offline**. `respx` intercepts HTTP and fails on any unmocked request, so a green run proves the parsers work rather than that the network happened to be up. Keep it that way: capture a real payload into [tests/fixtures/](tests/fixtures/) and test against that.
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
curl -s https://example.com/api/thing -o tests/fixtures/thing.json
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Trim large fixtures to the part that matters.
|
|
66
|
+
|
|
67
|
+
## Adding a source
|
|
68
|
+
|
|
69
|
+
Read [ADDING_SOURCES.md](ADDING_SOURCES.md), it builds a complete working source.
|
|
70
|
+
|
|
71
|
+
TLDR: implement `targets()` and `fetch()` from the protocol in [youpdated/sources/base.py](youpdated/sources/base.py), decorate the class with `@register`, and import it in [youpdated/sources/\_\_init\_\_.py](youpdated/sources/__init__.py). Reuse `entry_fields()` so your source accepts both the bare-value and mapping config shapes, and `parse_feed()` from [youpdated/sources/feed.py](youpdated/sources/feed.py) if the upstream is RSS/Atom. Third-party packages can register a source through a `youpdated.sources` entry point instead, without modifying this repo.
|
|
72
|
+
|
|
73
|
+
## Building a release
|
|
74
|
+
|
|
75
|
+
Publishing is done by [.github/workflows/release.yml](.github/workflows/release.yml) when a GitHub Release is published. It runs the full test matrix, builds, checks the tag matches the version in `pyproject.toml`, installs the built wheel to make sure it runs, and uploads to PyPI through trusted publishing. No PyPI token is stored.
|
|
76
|
+
|
|
77
|
+
1. Bump `version` in `pyproject.toml` and add the matching `CHANGELOG.md` entry in the same commit.
|
|
78
|
+
**A version is permanent:** PyPI refuses to replace an existing version even after a delete.
|
|
79
|
+
2. Optionally check the build locally first:
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
.venv/bin/pip install ".[publish]"
|
|
83
|
+
rm -rf dist build *.egg-info
|
|
84
|
+
.venv/bin/python -m build # -> dist/*.whl and dist/*.tar.gz
|
|
85
|
+
.venv/bin/twine check dist/* # metadata and README render check
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Running the workflow by hand (`workflow_dispatch`) with `dry_run` on does the same in CI without uploading.
|
|
89
|
+
3. Once that commit is on `main`, create a **signed** tag, verify it, and push it:
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
git tag -s v0.4.0 -m "v0.4.0"
|
|
93
|
+
git verify-tag v0.4.0
|
|
94
|
+
git push origin v0.4.0
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
4. Publish the release from that tag. `--verify-tag` aborts rather than letting `gh` create an unsigned tag of its own:
|
|
98
|
+
|
|
99
|
+
```sh
|
|
100
|
+
gh release create v0.4.0 --verify-tag --notes-from-tag
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Style
|
|
104
|
+
|
|
105
|
+
Match the surrounding code rather than a style guide, but generally:
|
|
106
|
+
|
|
107
|
+
- Type annotations throughout, with `from __future__ import annotations`
|
|
108
|
+
- Comments should explain why, especially where the code looks odd because an upstream service is odd.
|
|
109
|
+
The Brave source reads the REST API instead of the atom feed for a reason and that reason is a comment.
|
|
110
|
+
- Errors that reach the user name the offending value and say what was expected
|
|
111
|
+
- A source raises on genuine failure: the runner catches it and reports without killing the run and returns `[]` when it's working but has nothing to report
|
|
112
|
+
|
|
113
|
+
## Reporting things
|
|
114
|
+
|
|
115
|
+
Issue templates cover bugs, feature requests, and new-source requests. For security, see [SECURITY.md](SECURITY.md) and use private vulnerability reporting rather than a public issue.
|
|
116
|
+
|
|
117
|
+
## Use of AI
|
|
118
|
+
|
|
119
|
+
If you want to use AI to code for you or aid in your coding when contributing here, that's fine, just actually read it's output before submitting anything. No bloat or bad code is a goal you need to try to keep to.
|
|
120
|
+
|
|
121
|
+
Also, if you could acknowledge what AI did, that would be great!
|
youpdated-0.4.0/PKG-INFO
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: youpdated
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Simple update tracker for games, apps, and packages
|
|
5
|
+
Author-email: Void1-1 <161782542+Void1-1@users.noreply.github.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Void1-1/youpdated
|
|
8
|
+
Project-URL: Repository, https://github.com/Void1-1/youpdated
|
|
9
|
+
Project-URL: Issues, https://github.com/Void1-1/youpdated/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/Void1-1/youpdated/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: updates,release-tracking,scraper,rss,privacy,steam,itch,npm,pypi,github,gitlab,forgejo,youtube,notifications,cli
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Natural Language :: English
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
24
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
|
|
25
|
+
Classifier: Topic :: Utilities
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Requires-Python: >=3.11
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Requires-Dist: httpx[socks]>=0.27
|
|
31
|
+
Requires-Dist: feedparser>=6.0
|
|
32
|
+
Requires-Dist: packaging>=22
|
|
33
|
+
Requires-Dist: rich>=13.0
|
|
34
|
+
Requires-Dist: PyYAML>=6.0
|
|
35
|
+
Requires-Dist: platformdirs>=4.0
|
|
36
|
+
Provides-Extra: encryption
|
|
37
|
+
Requires-Dist: cryptography>=42.0; extra == "encryption"
|
|
38
|
+
Provides-Extra: dev
|
|
39
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
40
|
+
Requires-Dist: respx>=0.21; extra == "dev"
|
|
41
|
+
Requires-Dist: cryptography>=42.0; extra == "dev"
|
|
42
|
+
Provides-Extra: publish
|
|
43
|
+
Requires-Dist: build>=1.2; extra == "publish"
|
|
44
|
+
Requires-Dist: twine>=5.0; extra == "publish"
|
|
45
|
+
Dynamic: license-file
|
|
46
|
+
|
|
47
|
+
# Youpdated
|
|
48
|
+
|
|
49
|
+
[](https://github.com/Void1-1/youpdated/actions/workflows/tests.yml)
|
|
50
|
+
|
|
51
|
+
Youpdated keeps track of the games, apps, and packages you follow, and tells you what changed since you last checked in.
|
|
52
|
+
|
|
53
|
+
It is built to be **privacy-preserving, anonymous, and customizable**: no accounts, no API keys required, nothing stored anywhere non-local.
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
╭─ steam ──────────────────────────────────────────────────────────╮
|
|
57
|
+
│ Team Fortress 2 (440) │
|
|
58
|
+
│ • CLTF2 Halloween Cup 7d ago │
|
|
59
|
+
│ https://store.steampowered.com/news/app/440/view/6728766547… │
|
|
60
|
+
╰──────────────────────────────────────────────────────────────────╯
|
|
61
|
+
╭─ git ────────────────────────────────────────────────────────────╮
|
|
62
|
+
│ github.com/python/cpython │
|
|
63
|
+
│ • v3.14.7 5d ago │
|
|
64
|
+
│ https://github.com/python/cpython/releases/tag/v3.14.7 │
|
|
65
|
+
╰──────────────────────────────────────────────────────────────────╯
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Sources
|
|
69
|
+
|
|
70
|
+
| Source | What it watches |
|
|
71
|
+
| --- | --- |
|
|
72
|
+
| `git` | Releases, tags, and commits on GitHub, GitLab, Codeberg, or any Forgejo/Gitea server |
|
|
73
|
+
| `npm` | New versions of a JavaScript package |
|
|
74
|
+
| `homebrew` | New versions of a Homebrew formula or cask |
|
|
75
|
+
| `pypi` | New releases of a Python package, and if it's archived or deprecated |
|
|
76
|
+
| `steam` | Patch notes and news for a game |
|
|
77
|
+
| `itch` | Devlog posts and new builds of an itch.io game |
|
|
78
|
+
| `youtube` | New videos on a channel or playlist |
|
|
79
|
+
| `browser` | New Chrome, Brave, Firefox, and Edge releases |
|
|
80
|
+
| `feed` | Anything else with an RSS or Atom feed |
|
|
81
|
+
|
|
82
|
+
## Get started
|
|
83
|
+
|
|
84
|
+
### 1. Install
|
|
85
|
+
|
|
86
|
+
Works on macOS, Linux, and Windows. Needs **Python 3.11 or newer** (`python3 --version` to check). Then:
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
pipx install youpdated
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
[pipx](https://pipx.pypa.io) installs command-line tools so they don't interfere with anything else.
|
|
93
|
+
`pip install youpdated` works too.
|
|
94
|
+
|
|
95
|
+
Check it worked with:
|
|
96
|
+
|
|
97
|
+
```sh
|
|
98
|
+
youpdated --version
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### 2. Create your config
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
youpdated init
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
This creates a starter config file and **prints where it was placed**.
|
|
108
|
+
|
|
109
|
+
### 3. Add sources to watch
|
|
110
|
+
|
|
111
|
+
Name the source, then what to watch, in any form from [Where to find identifiers](#where-to-find-identifiers):
|
|
112
|
+
|
|
113
|
+
```sh
|
|
114
|
+
youpdated add git neovim/neovim
|
|
115
|
+
youpdated add npm react
|
|
116
|
+
youpdated add youtube @NASA
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
A link or handle that can only work with one source doesn't need to be named: `youpdated add https://github.com/neovim/neovim`.
|
|
120
|
+
|
|
121
|
+
The starter config comes with examples; `youpdated rm` to remove any you don't want.
|
|
122
|
+
|
|
123
|
+
Or open the file and list them under each source yourself:
|
|
124
|
+
|
|
125
|
+
```yaml
|
|
126
|
+
sources:
|
|
127
|
+
git:
|
|
128
|
+
- python/cpython
|
|
129
|
+
steam:
|
|
130
|
+
- 440
|
|
131
|
+
youtube:
|
|
132
|
+
- "@NASA"
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### 4. Run it
|
|
136
|
+
|
|
137
|
+
```sh
|
|
138
|
+
youpdated check
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**The first run will not display updates.** That's intended: it records everything that already exists, so later runs show only what's new. To see everything now, run `youpdated check --all`.
|
|
142
|
+
|
|
143
|
+
## Where to find identifiers
|
|
144
|
+
|
|
145
|
+
| Source you want watched | Example of what to put in config | How to find it |
|
|
146
|
+
| --- | --- | --- |
|
|
147
|
+
| A GitHub repo | `python/cpython` | The `owner/repo` part of the repo URL. A full `https://github.com/owner/repo` URL also works. |
|
|
148
|
+
| A GitLab, Codeberg, or Forgejo/Gitea repo | `https://gitlab.com/gitlab-org/cli` | The repo's URL as it appears in the address bar. |
|
|
149
|
+
| An npm package | `express`, `"@types/node"` | The package name on npmjs.com. **Quote names starting with `@`.** |
|
|
150
|
+
| A Homebrew formula or cask | `wget`, `cask:firefox` | The name after `brew install`. Put `cask:` in front of casks. |
|
|
151
|
+
| A PyPI package | `httpx` | The name after `pip install`. |
|
|
152
|
+
| A Steam game | `440` | The number in the store URL: `store.steampowered.com/app/`**`440`**`/Team_Fortress_2/`. The full store URL also works. |
|
|
153
|
+
| An itch.io game | `https://user.itch.io/game-name` | The game's page URL, exactly as it appears in the address bar. |
|
|
154
|
+
| A YouTube channel | `"@NASA"` | The `@handle` from the channel URL. **Keep the quotes with `@` characters** |
|
|
155
|
+
| A YouTube playlist | `playlist: PLxxxx` | The `list=` part of the playlist URL. A full playlist URL also works. |
|
|
156
|
+
| A browser | `chrome`, `brave`, `firefox`, `edge` | Just the name. |
|
|
157
|
+
| Anything else | `https://example.com/feed.xml` | Any RSS or Atom feed URL. Many sites publish one at `/feed.xml`, `/releases.atom`, or `/blog/rss`. |
|
|
158
|
+
|
|
159
|
+
## Basic commands
|
|
160
|
+
|
|
161
|
+
```sh
|
|
162
|
+
youpdated check # what's new since the last run
|
|
163
|
+
youpdated check --all # everything, not just what's new
|
|
164
|
+
youpdated check --since 7d # only things from the last week
|
|
165
|
+
youpdated check -s steam # only one source
|
|
166
|
+
youpdated check --test -v # check your config without fetching anything
|
|
167
|
+
youpdated add npm react # start watching something
|
|
168
|
+
youpdated remove npm react # stop watching it
|
|
169
|
+
youpdated uninstall # remove Youpdated files
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Privacy
|
|
173
|
+
|
|
174
|
+
- **No accounts or keys.** Every source works without sign in.
|
|
175
|
+
- **Everything stays local.** Nothing is sent anywhere except the sites you choose to watch.
|
|
176
|
+
- **Optional extras:** route everything through Tor or another proxy, and encrypt your config with a passphrase.
|
|
177
|
+
|
|
178
|
+
## Learn more
|
|
179
|
+
|
|
180
|
+
- [Configuration](https://github.com/Void1-1/youpdated/blob/main/docs/configuration.md): all options, ignoring update types, and how long history is kept
|
|
181
|
+
- [Usage](https://github.com/Void1-1/youpdated/blob/main/docs/usage.md): all commands, JSON and RSS output, and running on a schedule
|
|
182
|
+
- [Notifications](https://github.com/Void1-1/youpdated/blob/main/docs/notifications.md): get updates on your phone, Discord, Slack, or to a script
|
|
183
|
+
- [Privacy](https://github.com/Void1-1/youpdated/blob/main/docs/privacy.md): proxies, Tor, and details on what information is sent where
|
|
184
|
+
- [Encryption at rest](https://github.com/Void1-1/youpdated/blob/main/docs/encryption.md): lock your config and history behind a passphrase
|
|
185
|
+
- [Troubleshooting](https://github.com/Void1-1/youpdated/blob/main/docs/troubleshooting.md): common issues, and more about uninstalling
|
|
186
|
+
- [Contributing](https://github.com/Void1-1/youpdated/blob/main/CONTRIBUTING.md): installing from source, running the tests, and [adding a source](https://github.com/Void1-1/youpdated/blob/main/ADDING_SOURCES.md)
|