57c880a623892d4fca077262e831b1dd66cb1a4e
13
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e3fd8c67d4 |
feat: switch pixiv off — unregistered, unreachable, and refused at dispatch (406 phase 1)
Milestone 406 retires pixiv (rule 171) in two phases at the operator's explicit ask: switch it off, then later delete its code. This is the switch-off. Steps 2 and 3 ship together because each is a half-state of the other: unregistered but still in the extension, pixiv creator pages would offer a button the backend then refuses. Reachability removed, never gated (rule 22 - no flag, no `if platform == "pixiv"`): - platforms registry: pixiv unregistered, so /api/platforms, the source validator and quick-add all refuse it through their existing unknown-platform paths. - NATIVE_INGESTER_PLATFORMS: pixiv removed. - extension_service: pixiv's quick-add URL pattern removed (the Python half of the JS mirror). - extension: pixiv's host permissions, content-script match, platform entry and artist pattern removed; popup's pixiv branches removed; and the whole pixiv PKCE OAuth flow cut out of background.js. That last one could not wait for phase 2 - a webRequest listener on a host the manifest no longer grants is at best dead and at worst a startup failure for the entire background script. On startup the extension now also removes any pixiv refresh token a browser still holds in storage, for the same reason as the server-side credential cleanup (3980). - frontend: the extension card stops listing pixiv; SourceActions' copy of the native list drops it. platformColor keeps rendering a pixiv key so existing pixiv posts do not look broken. The guard, and why a registry change alone was not enough. A source outlives its platform: the live instance still had one ENABLED pixiv source (step 1). Tracing it: the scheduler only selects enabled rows and every platform lookup uses .get(), so a disabled row is inert - but re-enabling it and pressing Check would have routed pixiv, no longer native, straight into the gallery-dl branch, which still has a pixiv extractor. And a worker can pick up a still-enabled row before a deploy's migration runs. So run_download and verify_source_credential - the two functions every download and credential probe pass through - now refuse any platform not in the registry: an unsupported_url failure for downloads, and an inconclusive (None, not False) verify, since nothing was probed so nothing was rejected. Generic by registration, so it covers deviantart's leftovers too. Positive-controlled: a supported gallery-dl platform must still reach gallery-dl, or a guard that refused everything would pass (rule 167). Migration 0097 disables sources on retired platforms (pixiv, deviantart) and clears their failure state exactly as disabling through the app does (1285), so the stale row stops being scheduled and stops showing as failing. Nothing is deleted: removing a source can collide with uq_post_artist_external_id_null_source on real data, which is phase 2's step 6 to check. No post or image is touched. Tests: the known-platform lists drop pixiv and gain retirement assertions beside deviantart's; pixiv's positive extension cases become negative guards; the pixiv sidecar post-URL test is deleted with the behaviour it tested; quick-add rejects a pixiv URL. The pixiv client/downloader/ingester suites stay - that code stays until phase 2. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SHQB1YukL3VyvMK8rcbmV9 |
||
|
|
ddf896078c |
refactor(platforms): retire deviantart end-to-end (#3069)
CI / lint (push) Successful in 3s
CI / extension-version (push) Successful in 4s
CI / frontend-build (push) Successful in 23s
extension / lint (push) Successful in 26s
CI / backend-lint-and-test (push) Successful in 28s
CI / integration (push) Successful in 3m43s
Executes the 2026-07-05 product decision (FC downloaders = art-dedicated
services only), which removed Twitter/X and Bluesky but left deviantart
fully wired for seven weeks — the half-retired state rule 22 exists to
prevent.
Removed: the PlatformInfo module and its registry entry, the gallery-dl
extractor block, extension_service's artist-page pattern, the extension's
PLATFORMS + PLATFORM_ARTIST_PATTERNS entries, its manifest host permission
and content-script match, the frontend icon/colour/label, and the operator-
facing "supported platforms" list that still advertised it.
Two judgment calls, both recorded in migration 0088:
* existing `source` rows are DISABLED, not deleted. The row is the only
record of the artist's DeviantArt URL. Disabling is also required for
correctness rather than tidiness: with the platform unregistered the
download path falls through to gallery-dl, which carries its OWN
deviantart extractor, so an enabled row would have kept downloading
from a dropped platform.
* the `credential` row IS deleted — a live session cookie for a site FC
will never call again.
Adds the invariant whose absence is why manifest.json drifted in the first
place: nothing tied its domain lists back to the platform table. The
extension suite now asserts both directions, plus that no host permission
belongs to an unclaimed domain (`*://*/*` exempted — FC is self-hosted at
an operator-chosen URL the extension cannot enumerate).
Extension version 1.0.10 -> 1.0.11: ci.yml's guard hard-fails a packaged
extension change without a bump. No release is cut — build.yml's
sign-extension job only runs on main.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
8214afee1e |
fix(extension): normalize FC URL so credential push doesn't 405
The stored apiUrl was required to already carry the `/api` suffix, since
api.js builds requests as `${baseUrl}/credentials`. The options label read
"FC base URL", so entering the instance root -- the natural reading --
sent every request one path segment short: POST /credentials hit the Vue
SPA catch-all and came back 405, and GET /extension/manifest 404'd.
Worse, Test Connection reported success on it: the catch-all answers GET
/credentials with 200 HTML, so `r.ok` was true and the only affordance
meant to catch this misconfiguration actively masked it.
Normalize instead of validate (rules 92, 26):
- New lib/url.js: normalizeApiUrl / webRootFromApiUrl, one source shared
by the background client and the options page. Accepts either the
instance root or the API root.
- api.js normalizes on read, so configs already stored in the broken form
heal themselves without the operator reopening Settings.
- options.js stores the canonical form, echoes back what it saved, and
the test now asserts a JSON content-type -- killing the false green.
- 404/405 in request() now names the URL and points at the setting.
- Options label/placeholder state that both forms work.
Version 1.0.9 -> 1.0.10 in BOTH manifest.json and package.json; build.yml
resolves the release version from package.json, and a stale value there
would hit the cached ext-1.0.9 asset and republish the old XPI unsigned
against the new code.
Refs #2393
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
d80a5255ed |
feat(extension): in-app update prompt — popup banner + toolbar badge (#1489)
CI / lint (push) Successful in 3s
extension / lint (push) Successful in 10s
CI / frontend-build (push) Successful in 24s
CI / backend-lint-and-test (push) Successful in 39s
CI / integration (push) Successful in 3m51s
extension / lint (pull_request) Successful in 10s
The extension is installed per-instance from the operator's FC host, so Firefox's
static update_url can't apply (each instance has a different host) and updates
were fully manual. Add a self-hosted-friendly update surface that reuses the
existing public GET /api/extension/manifest ({version, latest_url, sha256}):
- lib/api.js: getExtensionManifest().
- background.js: checkForUpdateInfo() compares the instance's latest published
version against runtime.getManifest().version (dotted-numeric compare so
1.0.10 > 1.0.9); CHECK_UPDATE message handler; refreshUpdateBadge() sets a
toolbar badge via browser.action; a daily browser.alarms check plus on
startup/installed. New 'alarms' permission (non-prompting).
- popup: an 'Update available — vX' banner with an Update button that opens the
signed XPI (web root, /api stripped like OPEN_ARTIST_PAGE) → Firefox's native
install prompt. Never blocks the popup on a failed check.
No backend changes (endpoint already exists). Bump 1.0.8→1.0.9 so this ships;
from here on updates surface themselves instead of needing a manual reinstall.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
69b5637bd6 |
fix(extension): show Add-to-FC button on subscribed Patreon creators (#1485)
CI / lint (push) Successful in 3s
extension / lint (push) Successful in 11s
CI / frontend-build (push) Successful in 21s
CI / backend-lint-and-test (push) Successful in 30s
CI / integration (push) Successful in 3m47s
extension / lint (pull_request) Successful in 9s
Symptom (operator-flagged): the extension injected the Add-as-source button on a Patreon creator you had NOT subscribed to, but it disappeared once you were subscribed — the opposite of when it's useful. Root cause (extension logic, not Patreon security): Patreon serves a creator under three URL shapes — bare patreon.com/Atole, patreon.com/c/Atole, and patreon.com/cw/Atole (the 'creator workspace' URL you land on once subscribed; documented in patreon_resolver._VANITY_RE). The button's artist-page gate (PLATFORM_ARTIST_PATTERNS.patreon in platforms.js) and its byte-mirror probe pattern (_PLATFORM_PATTERNS in extension_service._derive) only matched the bare single-segment form and explicitly excluded c/. So the subscribed-view URL failed the gate → no button. The ingestion resolver already handled all three; only these two gates were too narrow. Fix: both regexes now accept optional cw/ and c/ prefixes and drop the strict single-segment end-anchor, so a creator's inner page (/cw/Atole/posts, /Atole/membership) also matches — robust to whatever exact shape the subscribed view uses. Nav-page exclusions (home/search/messages/notifications/library/ settings/posts + post permalinks) preserved. New unit test covers all three prefixes, sub-paths, and nav-page rejection (both regexes validated identically). Bump extension 1.0.7→1.0.8 so a fresh signed XPI ships the fix (also exercises batch-5 web-ext-10's AMO sign path end-to-end on the main build). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
d3245f0c22 |
feat(ext): verify cookies in-browser before uploading (1.0.7)
CI / intcore (push) Successful in 8m45s
extension / lint (pull_request) Successful in 14s
CI / backend-lint-and-test (push) Successful in 37s
CI / frontend-build (push) Successful in 40s
extension / lint (push) Successful in 36s
CI / lint (push) Successful in 4s
CI / intimp (push) Successful in 4m4s
CI / intapi (push) Successful in 8m2s
Pre-upload verify request: after capturing the live browser cookies,
hit a known authenticated endpoint with credentials:'include' from the
extension's background context. If the platform reports we're not
logged in, abort the upload so we don't overwrite FC-side credentials
with stale data.
- platforms.js: add `verify` config per cookie-auth platform
- hentaifoundry: HEAD /?enterAgree=1 (mirrors gallery-dl's HF
_init_site_filters; same 401 path the operator hit 2026-06-03)
- patreon: GET /api/current_user (clean 401 when logged out)
- subscribestar, deviantart: no stable auth endpoint, skip verify
- cookies.js: verifyCookiesForPlatform() returns {ok, status, reason}.
ok=true/false/null tri-state — null = verify not configured, caller
treats as "proceed".
- background.js EXPORT_COOKIES + EXPORT_ALL_COOKIES: verify gates the
upload; failures bubble up with the platform's name + reason.
- popup.js: success message now appends "(verified ✓)" when applicable.
- manifest + package.json: 1.0.6 → 1.0.7.
|
||
|
|
d65f0b2091 |
feat(extension): probe shows current state before click; v1.0.6
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 19s
CI / backend-lint-and-test (push) Successful in 20s
extension / lint (push) Successful in 13s
CI / intimp (push) Successful in 3m41s
CI / intapi (push) Successful in 7m11s
CI / intcore (push) Successful in 7m41s
Operator-asked 2026-05-31 (during sidecar synthetic anchor cleanup): "the add source/subscription button idea to the firefox extension so it can tell me if a source/artist is added or not and offer an option to add it if it isn't." Plan tracked in Scribe task #507. ## Backend - `ExtensionService.probe(url)` — read-only resolution. Reuses `_derive` for platform+slug, then 2 SELECTs. Returns one of: - `source_match` (exact (artist, platform, url) Source exists) - `artist_match` (artist exists, this URL isn't a Source yet; collapses the sidecar-synthetic-only case from v26.06.01.0) - `new` (neither exists) - `unknown_platform` (URL didn't match any artist-page regex) - `GET /api/extension/probe?url=...` route with `X-Extension-Key` auth posture matching `/quick-add-source`. Read-only, side-effect free. - 6 backend tests in tests/test_api_extension.py covering each state + auth + invalid URL. ## Extension - `api.js`: `probeSource(url)` mirroring `quickAddSource` shape. - `background.js`: `PROBE_SOURCE` + `OPEN_ARTIST_PAGE` handlers. The latter strips the `/api` suffix from configured `apiUrl` (placeholder format per options.html) and opens `${base}/artist/{slug}` in a new tab via `browser.tabs.create`. - `content-script.js`: probe-first render — on page-load and SPA navigation, asks the backend for the URL's state and renders the chip in the matching color/copy on FIRST paint instead of flashing generic "Add" and updating after. Click handler branches: `source_match` → OPEN_ARTIST_PAGE; `artist_match`/`new` → existing ADD_AS_SOURCE flow (then re-probes so the chip flips green immediately, no wait for next nav). - `content-script.css`: three state-color modifiers (--new, --artist-match, --source-match) on the FC parchment-on-slate palette. Sage for already-added, amber for artist-exists, accent orange for new. ## Versioning - `extension/manifest.json` + `extension/package.json` → 1.0.6. build.yml's sign-extension job will fire on push to main since no `ext-1.0.6` Forgejo/Gitea release exists yet — exercises the regenerated AMO keys end-to-end. ## Behavior on the sidecar-synthetic case Filesystem-imported "Dymkens"-style artist with only a sidecar synthetic Source: probe returns `artist_match` (not `new`), so the chip reads "+ Add Patreon source to Dymkens" rather than offering to recreate the artist. Clicking adds the real Source; existing `_source_for_sidecar` preference logic (v26.06.01.0) routes future gallery-dl Posts to the real one. |
||
|
|
2394e47370 |
fix(hentaifoundry): inject host-only PHPSESSID/CSRF duplicates + extension preserves browser hostOnly
Operator-flagged 2026-05-27: HF source check 401'd on
`HEAD /?enterAgree=1` even with valid login cookies. Root cause is the
combination of (1) gallery-dl's HF extractor checking
`self.cookies.get("PHPSESSID", domain="www.hentai-foundry.com")` with
`requests`' EXACT domain matching, and (2) the extension's cookies.js
forcibly rewriting every captured cookie to a leading-dot subdomain-wide
form. HF's PHPSESSID is browser-stored as host-only on
`www.hentai-foundry.com`; the rewrite re-anchored it to
`.hentai-foundry.com`, which `cookies.get(...)` no longer matches even
though the cookie is still sent on actual HTTP requests (RFC 6265
subdomain rules). The extractor falls into its unauthenticated
`?enterAgree=1` fallback, which 401s (Cloudflare or HF's anti-bot HEAD
gating).
Two-part fix, no operator action required for existing stored cookies:
1. **Backend** (`credential_service._augment_cookies`) — refactored from
the subscribestar-only single function into a per-platform dispatcher.
New `_augment_hentaifoundry` parses the materialized netscape file
and, for each `.hentai-foundry.com` entry whose name is PHPSESSID or
YII_CSRF_TOKEN, appends a host-only duplicate
(`www.hentai-foundry.com\tFALSE\t...`). Originals preserved. Three
new tests pin: injection fires + originals preserved; idempotent
when host-only already exists; doesn't touch unrelated cookies
(e.g. `_ga`).
2. **Extension** (`cookies.js`) — `toNetscapeFormat` now respects
`c.hostOnly` from the browser instead of blindly forcing a
leading-dot subdomain-wide form. Host-only cookies are written with
the bare host + FALSE flag; non-host-only cookies retain the
leading-dot + TRUE form. Forward-compat — fresh captures from
v1.0.5+ no longer need the backend's host-only duplication.
Extension bumped 1.0.4 → 1.0.5; manifest + package.json in lockstep.
After deploy: the next HF source check on the operator's already-stored
cookies will succeed because the materialized cookies.txt now contains
host-only PHPSESSID. No browser re-export needed.
|
||
|
|
74dac6b960 |
fix(extension+migration): MV3 CSP opt-out from upgrade-insecure-requests (v1.0.4) + alembic 0023 drops the ck_tag_fandom_requires_character check before the type swap
extension/manifest.json: add content_security_policy.extension_pages = "script-src 'self'; object-src 'self';" — explicitly omits the upgrade-insecure-requests directive that MV3 inherits by default. Without this, every fetch(http://curator.../...) silently upgrades to https:// at the browser layer (Sec-Fetch-Site=same-origin, NS_ERROR_GENERATE_FAILURE), regardless of about:config. Bump XPI version 1.0.3 → 1.0.4 so a fresh signed build replaces the cached one. Operator-troubleshot 2026-05-26 via Inspect-the-extension dev tools showing the silent scheme upgrade. alembic 0023: drop ck_tag_fandom_requires_character before the tag_kind type swap and recreate after. Postgres can't resolve `kind = 'character'` across the rename (column on tag_kind_old, literal binds to new tag_kind → "operator does not exist"). Same dance on downgrade. Banked under reference_tag_kind_enum_swap_check_drop. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
5587a76606 | fix(ext-ci): replace jq with python3 (jq not in ci-python image) + bump ext 1.0.2→1.0.3 (escape AMO 'version already exists' from prior partial-failure run) + add rollback to prevent empty cache-release tombstones — Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | ||
|
|
c06cbc0abe | feat(ci): inline extension sign into build.yml + Forgejo Release Assets as XPI cache (v26.05.25.5) — bump ext to 1.0.2 — Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | ||
|
|
dc3bce7fc1 | chore(ext): bump to 1.0.1 to trigger initial sign-and-publish — Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | ||
|
|
a06c4f009f |
fc3g(ext): bootstrap extension/ skeleton (manifest, package, icon, README)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |