Compare commits

...
4 Commits
Author SHA1 Message Date
bvandeusen 89c83ee5de Merge pull request 'fix(extension): Add-to-FC button on subscribed Patreon creators + v1.0.8 (#1485)' (#230) from dev into main
CI / lint (push) Successful in 3s
Build images / build-agent (push) Successful in 8s
Build images / build-ml (push) Successful in 8s
extension / lint (push) Successful in 8s
CI / frontend-build (push) Successful in 20s
CI / backend-lint-and-test (push) Successful in 30s
CI / integration (push) Successful in 3m46s
Build images / sign-extension (push) Successful in 5m8s
Build images / build-web (push) Successful in 10s
2026-07-13 17:56:32 -04:00
bvandeusenandClaude Opus 4.8 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>
2026-07-13 17:50:07 -04:00
bvandeusen d3192f1843 Merge pull request 'chore(deps): web-ext 8→10 (Renovate batch 5) (#1450)' (#229) from dev into main
Build images / sign-extension (push) Successful in 3s
CI / lint (push) Successful in 3s
Build images / build-ml (push) Successful in 7s
Build images / build-agent (push) Successful in 8s
Build images / build-web (push) Successful in 6s
extension / lint (push) Successful in 9s
CI / frontend-build (push) Successful in 19s
CI / backend-lint-and-test (push) Successful in 35s
CI / integration (push) Successful in 3m48s
2026-07-13 16:36:26 -04:00
bvandeusenandClaude Opus 4.8 51749e05db chore(deps): update web-ext 8→10 (batch 5) (#1450)
CI / lint (push) Successful in 2s
extension / lint (push) Successful in 9s
CI / frontend-build (push) Successful in 19s
CI / backend-lint-and-test (push) Successful in 28s
CI / integration (push) Successful in 3m58s
extension / lint (pull_request) Successful in 9s
Renovate dep-dashboard batch 5. web-ext is the extension's build/lint/sign
CLI (devDependency only; no extension source changes).

Verified against the 8→10 changelog + FC's actual usage:
- All CLI flags we use survive unchanged: --source-dir, --no-config-discovery,
  --ignore-files, --overwrite-dest (build), --channel/--api-key/--api-secret
  (sign). No removed/renamed flags for lint/build/sign.
- v9's one breaking change (.js config files rejected) does NOT apply: we pass
  --no-config-discovery on every command and ship no config file.
- Node: web-ext 10 baselines Node 22. The lint job runs on node:24-bookworm-slim;
  the load-bearing AMO sign job runs on ci-python:3.14 which installs Node 24
  (CI-runner NODE_MAJOR=24) — both satisfy it. Sign is cache-skipped this push
  (extension version unchanged at 1.0.7) but is verified compatible for the next
  version bump.
- The bundled addons-linter jumps to 10.1.0 — the extension.yml lint job (web-ext
  lint over the MV3 manifest) is the CI verifier for any new manifest findings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 16:30:11 -04:00
5 changed files with 85 additions and 6 deletions
+7 -2
View File
@@ -35,9 +35,14 @@ class InvalidUrlError(Exception):
# reviewers catch drift.
_PLATFORM_PATTERNS: list[tuple[str, re.Pattern[str]]] = [
("patreon", re.compile(
# Three creator URL shapes — bare (patreon.com/Atole), `c/`, and `cw/`
# (the "creator workspace" URL served once subscribed, see
# patreon_resolver._VANITY_RE). A trailing sub-path is allowed so a
# creator's inner page still derives the slug. Nav pages stay excluded.
r"^https?://(?:www\.)?patreon\.com/"
r"(?!home$|search\b|messages\b|notifications\b|library\b|settings\b|posts\b|c/)"
r"(?P<slug>[^/?#]+)/?$",
r"(?:cw/|c/)?"
r"(?!(?:home|search|messages|notifications|library|settings|posts)(?:[/?#]|$))"
r"(?P<slug>[^/?#]+)",
re.IGNORECASE,
)),
("subscribestar", re.compile(
+10 -1
View File
@@ -86,7 +86,16 @@ const PLATFORMS = {
* script to decide whether to show the floating "Add as source" button.
*/
const PLATFORM_ARTIST_PATTERNS = {
patreon: /^https?:\/\/(www\.)?patreon\.com\/(?!home$|search\b|messages\b|notifications\b|library\b|settings\b|posts\b|c\/)[^/?#]+\/?$/i,
// Patreon serves the same creator under three URL shapes (see backend
// patreon_resolver._VANITY_RE): bare `patreon.com/Atole`, `c/` prefix, and
// `cw/` "creator workspace" — the last is the URL you land on once you're
// SUBSCRIBED, which is exactly when the button matters. Match all three, and
// drop the single-segment end-anchor so a creator's inner page
// (…/cw/Atole/posts, …/Atole/membership) also injects the button. Nav pages
// (home/search/…/posts permalink) stay excluded. Mirrors extension_service
// ._PLATFORM_PATTERNS — keep in sync (operator-flagged 2026-07-13: button
// vanished once subscribed because the old pattern only matched the bare root).
patreon: /^https?:\/\/(www\.)?patreon\.com\/(?:cw\/|c\/)?(?!(?:home|search|messages|notifications|library|settings|posts)(?:[\/?#]|$))[^/?#]+/i,
subscribestar: /^https?:\/\/(www\.)?subscribestar\.(com|adult)\/(?!feed$|messages$|library$)[^/?#]+\/?$/i,
hentaifoundry: /^https?:\/\/(www\.)?hentai-foundry\.com\/user\/[^/?#]+/i,
deviantart: /^https?:\/\/(www\.)?deviantart\.com\/(?!home$|watch\b|tag\b|browse\b)[^/?#]+\/?$/i,
+1 -1
View File
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "FabledCurator",
"version": "1.0.7",
"version": "1.0.8",
"description": "Export cookies from supported platforms to FabledCurator and add creators as sources in one click.",
"browser_specific_settings": {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "fabledcurator-extension",
"version": "1.0.7",
"version": "1.0.8",
"private": true,
"description": "Firefox extension for FabledCurator",
"scripts": {
@@ -10,6 +10,6 @@
"sign": "web-ext sign --source-dir=. --no-config-discovery --ignore-files package.json package-lock.json web-ext-artifacts node_modules README.md .gitignore --channel=unlisted --api-key=$WEB_EXT_API_KEY --api-secret=$WEB_EXT_API_SECRET"
},
"devDependencies": {
"web-ext": "^8.0.0"
"web-ext": "^10.0.0"
}
}
+65
View File
@@ -0,0 +1,65 @@
"""Unit tests for ExtensionService._derive — the URL → (platform, slug)
parser that gates the browser extension's "Add as source" button and pulls
the creator slug on probe/add.
Regression cover for #1485: Patreon serves the same creator under three URL
shapes — bare `patreon.com/Atole`, `c/`, and `cw/` (the "creator workspace"
URL you land on once SUBSCRIBED). The button used to vanish while subscribed
because the pattern only matched the bare root and excluded `c/`.
_derive is pure URL parsing (no DB / no async), so a session-less instance is
fine to exercise directly.
"""
import pytest
from backend.app.services.extension_service import (
ExtensionService,
InvalidUrlError,
UnknownPlatformError,
)
_svc = ExtensionService(None)
@pytest.mark.parametrize(
"url, slug",
[
# All three Patreon creator prefixes resolve to the same vanity slug.
("https://www.patreon.com/Atole", "Atole"),
("https://www.patreon.com/c/Atole", "Atole"),
("https://www.patreon.com/cw/Atole", "Atole"), # subscribed-view URL
# A creator's inner page still derives the slug (trailing sub-path).
("https://www.patreon.com/cw/Atole/posts", "Atole"),
("https://www.patreon.com/Atole/membership", "Atole"),
("https://patreon.com/c/Atole", "Atole"), # bare host, no www
],
)
def test_derive_patreon_creator_urls(url, slug):
platform, got = _svc._derive(url)
assert platform == "patreon"
assert got == slug
@pytest.mark.parametrize(
"url",
[
# Patreon's own nav pages must never read as a creator slug.
"https://www.patreon.com/home",
"https://www.patreon.com/settings",
"https://www.patreon.com/search",
"https://www.patreon.com/messages",
"https://www.patreon.com/library",
"https://www.patreon.com/notifications",
"https://www.patreon.com/posts/12345", # post permalink
"https://www.patreon.com/settings/billing", # nav sub-page
],
)
def test_derive_patreon_nav_pages_rejected(url):
with pytest.raises(UnknownPlatformError):
_svc._derive(url)
def test_derive_rejects_missing_scheme():
with pytest.raises(InvalidUrlError):
_svc._derive("patreon.com/Atole")