Skip to main content

Obsidian Yt Archiver

@magistr/obsidian-yt-archiverv2026.09.19.2· 17d agoMODELS
01README

Scan Obsidian vault for YouTube links, archive in TubeArchivist, generate reference notes

02Release Notes

2026.09.19.2

Changed

  • Repo-wide maintenance release: version bump to republish the current source. No schema change.
03Models1
@magistr/obsidian-yt-archiverv2026.09.19.2extensions/models/obsidian_yt_archiver.ts

Global Arguments

ArgumentTypeDescription
vaultPathstringAbsolute path to Obsidian vault
fn scan(folder?: string)
Scan vault for YouTube links
ArgumentTypeDescription
folder?stringSubfolder to scan (relative to vault root)
fn archive()
Queue YouTube videos in TubeArchivist for download
fn resolve()
Fetch metadata for archived videos from TubeArchivist
fn sync(folder?: string)
Scan vault, archive new videos, resolve metadata — all in one pass
ArgumentTypeDescription
folder?stringSubfolder to scan

Resources

scan(infinite)— YouTube links found in vault
archive(infinite)— Archive operation result
resolved(infinite)— Resolved video metadata from TubeArchivist
04Previous Versions7
2026.09.17.1

2026.09.17.1

Changed

  • Repo-wide maintenance release: version bump to republish the current source. No schema change.
2026.08.19.1
2026.08.02.1

2026.08.02.1

Fixes the remaining seven latent bugs -- LB1, LB3, LB4, LB5, LB6, LB7, LB8 -- tracked in the LOCAL obsidian-yt-archiver-latent-bugs issue-lifecycle model (NEVER filed to the swamp.club Lab -- see CLAUDE.md's anti-bypass rule). LB2 was already fixed in 2026.08.01.1 and is untouched here. No new npm/jsr dependency was added (jsr:@std/path@1 remains the only non-builtin import; deno.lock is unchanged) and no resource schema or globalArguments shape changed -- the version bump is a pure identity upgrade.

  • LB1 (MEDIUM, path traversal / request-forgery via videoIds): added a shared taVideoPath(id) helper (encodeURIComponent) used at all three GET-path build sites (archive/resolve/sync). A ../-laden or absolute-URL-shaped id is now percent-encoded into ONE opaque /api/video/<id>/ path segment instead of reaching a different TA endpoint on the same host. Identity for every benign id used across the suites ([A-Za-z0-9_-]) -- every byte-frozen contract/methods URL pin stays unchanged.
  • LB3 (MEDIUM, conflated error handling -> mass re-queue): taApi now throws a typed TaHttpError carrying .status on any non-2xx/redirect/ non-JSON response. A shared isNotArchived(e) predicate treats ONLY a genuine 404 as "not archived" (queue/unresolved); every other failure (401/403/500/502/503/timeout/network/redirect/non-JSON) is re-thrown and surfaces instead of being silently re-queued alongside real not-archived ids.
  • LB4 (MEDIUM, no fetch timeout): every taApi call now passes an AbortController-backed signal with a 30s default timeout (DEFAULT_REQUEST_TIMEOUT_MS), cleared in a single finally so no timer ever leaks (satisfies Deno's op-sanitizer on every code path: success, redirect-throw, !ok-throw, non-JSON-throw, network-throw).
  • LB5 (LOW-MED, unbounded sequential per-id fetch): a shared assertVideoIdCap(ids) REJECTS (never silently slices/drops) any id list longer than MAX_VIDEO_IDS = 500, checked once per method immediately after the id list is resolved (archive/resolve/sync), before any fetch fires. Sequential per-id execution order is unchanged -- no batching/concurrency was introduced.
  • LB6 (LOW, error body truncated to 200 raw chars): replaced the raw 200-char slice with redactBody() -- collapses whitespace runs and caps the result at 120 chars (plus an ellipsis) before it is interpolated into any thrown message. The auth token was already header-only and remains never part of this text.
  • LB7 (LOW, default redirect:"follow"): taApi now passes redirect: "manual" on every call, plus an explicit guard that throws on any 3xx status or an "opaqueredirect" response type (surfaced, never silently followed to a possibly-different host) and a defense-in-depth host-revalidation check against the operator-configured tubearchivistUrl.
  • LB8 (LOW, non-JSON 200 -> blank "archived" record): taApi gained an expectJson parameter (default true). Every per-id metadata GET check now surfaces (throws) on a 2xx response whose content-type is not application/json, instead of silently returning {} and recording a blank archived: true entry. The two fire-and-forget POST calls (/api/download/, download_pending) pass expectJson: false and keep their existing {}-on-non-JSON behavior -- zero POST-shape regression.
  • Test suites: flipped every LB1/LB3/LB4/LB5/LB6/LB7/LB8 characterization pin in obsidian_yt_archiver_adversarial_test.ts from "the bug is present" to fixed (... FIXED) (mostly assertRejects), added new cases (LB3's 401/500 split, LB5's 501-id cap-reject, LB7's 302-reject), and flipped the coverage suite's no-content-type-header-200 test to assertRejects (LB8). Rewrote the property suite's (b)/(b-resolve) "archive()/ resolve() never throw for ANY status" properties -- directly contradicted by the LB3/LB7/LB8 fixes -- into a (b1) never-throws property scoped to {genuine JSON-200, genuine 404} and a NEW (b2) surfaces property scoped to {redirect, 4xx/5xx, non-JSON 200}, partitioning every GET-check outcome the suite generates with no overlap. LB2's fix and every byte-frozen contract/methods pin stay green, unchanged.
  • Adversarial + security review follow-up: added two coverage-closing tests the reviews flagged as untested claims -- a raw network-level failure (fetch() itself rejecting, not an HTTP error response) surfacing correctly through the LB3 catch/rethrow chain, and the LB7 host-revalidation defense-in-depth branch (previously unreachable by any stub, since a directly-constructed Response always leaves url empty -- now exercised via Object.defineProperty shadowing res.url).
  • manifest.yaml and model.version bumped to 2026.08.02.1.
2026.08.01.1

2026.08.01.1

Fixes LB2 (HIGH, path traversal), tracked in the LOCAL obsidian-yt-archiver-latent-bugs issue-lifecycle model (NEVER filed to the swamp.club Lab -- see CLAUDE.md's anti-bypass rule). scan's and sync's folder method-argument was concatenated directly into vaultPath with no containment check, so folder: "../outside" (or a deeper/absolute escape) walked arbitrary host directories via walkMd and read every non-hidden .md file outside the vault.

  • Added a shared pure guard, assertFolderWithinVault(vaultPath, folder), using jsr:@std/path@1's resolve/relative/isAbsolute -- LEXICAL only (never Deno.realPath, since the vault commonly lives under a symlinked temp root, e.g. macOS /var resolving to /private/var, and realPath would break every legitimate scan of such a vault). Rejects an absolute folder, or a vault-relative resolved path that is .. or begins with ../. Invoked identically at both scan.execute and sync.execute, before walkMd runs -- one shared helper, no divergent inline re-check.
  • Flipped the LB2 characterization pin in obsidian_yt_archiver_adversarial_test.ts from "the escape succeeds" to assertRejects, for both scan and sync, and added deeper rejection cases (.., ../.., notes/../../outside, and an absolute path) for both methods. Every previously-green legit-subfolder test (scan/sync folder=notes/Clippings/Sub) and the does-not-exist -> Deno.errors.NotFound test stay green: a contained-but-nonexistent folder passes the guard and still fails at readDir, unchanged.
  • Added the jsr:@std/path@1 dependency; deno.lock regenerated on deno 2.8.3. No other runtime behavior changes -- LB1 and LB3-LB8 remain latent/tracked in the same local model, unaffected by this change.
  • manifest.yaml and model.version bumped to 2026.08.01.1.
2026.07.16.2

Release 2026.07.16.2 — align model versions with manifests

Maintenance release across the @magistr extensions. For most packages this carries no functional change: the only edit is the model's version: field, brought back in line with its manifest version so the published model type version and the package version no longer drift.

Functional changes in this release are limited to:

  • anime-cron: normalizeTitle now strips a ": subtitle" suffix and a trailing parenthesized year before comparison, fixing dedup false-misses where the torrent title carries a subtitle or year that the AniList romaji does not.

  • arckit: first publish. Standalone ArcKit port — a 12-phase architecture governance state machine with 65 bundled templates, driven by a bundled skill.

Also tracks three extensions (kaiten, observability-agent, music-library) that previously existed only as untracked working-tree directories, recovered from stashes.

2026.05.25.1

Merge pull request #4 from umag/extensions/magistr-grade-a-workspace

extensions: stage 15 @magistr extensions as Grade A workspace dirs + wire CI

2026.03.28.1
05Stats
A
100 / 100
Downloads
15
Archive size
11.1 KB
  • Has README or module doc2/2earned
  • README has a code example1/1earned
  • README is substantive1/1earned
  • Most symbols documented1/1earned
  • No slow types (deprecated)1/1earned
  • Dependencies pass trust audit2/2earned
  • Has description1/1earned
  • Platform support declared (or universal)2/2earned
  • License declared1/1earned
  • Verified public repository2/2earned
06Platforms
07Labels