Files
FabledCurator/extension/test/artist-url-samples.json
T
bvandeusenandClaude Opus 5.5 4c75dd0f88
CI and images / lint (push) Successful in 2s
CI and images / extension-version (push) Successful in 2s
CI and images / extension-test (push) Successful in 20s
CI and images / frontend-build (push) Successful in 24s
CI and images / backend-lint-and-test (push) Successful in 32s
CI and images / integration (push) Successful in 2m22s
CI and images / build-agent (push) Successful in 5s
CI and images / sign-extension (push) Successful in 3m13s
CI and images / build-web (push) Successful in 1m42s
CI and images / smoke-web (push) Successful in 54s
CI and images / promote (push) Successful in 1s
feat: the extension adds Discord channels to an artist you pick, and its tests gate the XPI (milestone 429)
Server (#4420)
- extension_service gains a Discord pattern (server or channel, jump links,
  ptb/canary; not DMs or threads), mirrored in platforms.js and pinned by
  the shared artist-url-samples.json.
- probe on a Discord URL matches the source by ids under any artist, reports
  a whole-server source as covering the channel, suggests the artist who owns
  another source on the same server, and names server/channel via the stored
  token (best-effort, bounded, no rate-limit waits).
- quick-add takes artist_id / artist_name; Discord URLs are stored canonical.

Extension (#4421, #4422)
- Content script on discord.com; SPA navigation by URL polling (the old
  pushState patch ran in the isolated world and never fired); stale probes
  are dropped.
- Discord chip opens an Add panel: this channel or the whole server, and the
  suggested artist / a search / a new name.
- Popup: sources show artist, platform and state; a Discord token export is
  verified by FC and the result shown. Token capture covers ptb/canary.
- Pure logic in lib/chip.js and lib/popup-format.js, with specs.

CI (#4423)
- extension.yml's lane (web-ext lint, vitest, XPI contents) moves into
  build.yml as extension-test and joins the needs of sign-extension,
  build-web and build-agent. As a separate workflow it gated nothing: a red
  extension suite still signed and shipped the XPI (rule 177).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVjrnpQjRgHdvq95rASoiR
2026-09-24 23:31:38 -04:00

176 lines
7.2 KiB
JSON

{
"$comment": [
"THE SHARED ARTIFACT for the JS<->Py artist-pattern mirror (issue #3093).",
"",
"extension/lib/platforms.js PLATFORM_ARTIST_PATTERNS and",
"backend/app/services/extension_service.py _PLATFORM_PATTERNS are two hand-kept",
"copies of one table, and they gate OPPOSITE HALVES of a single interaction:",
"the JS copy decides whether the 'Add to FC' button appears, the Python copy",
"decides whether the resulting POST is accepted. Drift is therefore never",
"cosmetic -- JS looser than Py shows a button that 400s, Py looser than JS",
"silently never offers a button for a URL the backend would take. Issue #1485",
"was the second of those, and its fix had to be applied to both files by hand.",
"",
"Neither runtime imports the other. This file is the shared artifact instead:",
"both suites read it and assert it against their OWN copy of the patterns, so",
"a change to one copy alone fails the other runtime's suite.",
" - extension/test/platforms.spec.js (vitest)",
" - tests/test_extension_artist_patterns.py (pytest)",
"",
"Lives under extension/test/ because that path is excluded from BOTH the XPI",
"file set and the extension version derivation (see scripts/packaging.sh:",
"NOT_PACKAGED_TRACKED and NOT_VERSION_RELEVANT both carry 'test/**'), so",
"adding samples here never ships bytes and never forces a re-sign.",
"",
"Adding a case: put the URL in the platform's match/no_match list with a",
"'why'. Run both suites. If only one goes red, you have found drift, which is",
"the entire point of the file.",
"",
"'slug' is read by the Python side only -- its _derive returns (platform,",
"slug) where the JS isArtistPage returns a boolean. It is not optional on a",
"match entry; the backend deriving the WRONG slug from a URL both copies",
"agree on is its own defect class, and this pins it."
],
"patreon": {
"match": [
{
"url": "https://www.patreon.com/maewix",
"slug": "maewix",
"why": "bare creator root"
},
{
"url": "https://patreon.com/maewix",
"slug": "maewix",
"why": "www is optional"
},
{
"url": "http://patreon.com/maewix",
"slug": "maewix",
"why": "http as well as https"
},
{
"url": "https://www.patreon.com/c/Atole",
"slug": "Atole",
"why": "#1485: the /c/ creator shape"
},
{
"url": "https://www.patreon.com/cw/Atole",
"slug": "Atole",
"why": "#1485: /cw/ is the 'creator workspace' URL Patreon serves once you are SUBSCRIBED -- exactly when the button matters most, and exactly what the pre-#1485 pattern missed"
},
{
"url": "https://www.patreon.com/cw/Atole/posts",
"slug": "Atole",
"why": "#1485: a creator inner page still derives the creator"
},
{
"url": "https://www.patreon.com/Atole/membership",
"slug": "Atole",
"why": "#1485: inner page on the bare shape"
}
],
"no_match": [
{ "url": "https://www.patreon.com/home", "why": "nav page, not a creator" },
{ "url": "https://www.patreon.com/search", "why": "nav page" },
{ "url": "https://www.patreon.com/messages", "why": "nav page" },
{ "url": "https://www.patreon.com/notifications", "why": "nav page" },
{ "url": "https://www.patreon.com/library", "why": "nav page" },
{ "url": "https://www.patreon.com/settings", "why": "nav page" },
{ "url": "https://www.patreon.com/posts", "why": "nav page; also the post-permalink prefix" },
{ "url": "https://www.patreon.com/home/anything", "why": "a nav page's inner path is still not a creator" },
{ "url": "https://www.patreon.com/settings/profile", "why": "as above" }
]
},
"subscribestar": {
"match": [
{
"url": "https://www.subscribestar.com/foobar",
"slug": "foobar",
"why": "creator root on the .com TLD"
},
{
"url": "https://subscribestar.adult/foobar",
"slug": "foobar",
"why": "creator root on the .adult TLD"
},
{
"url": "https://subscribestar.adult/foobar/",
"slug": "foobar",
"why": "trailing slash is tolerated"
}
],
"no_match": [
{ "url": "https://subscribestar.adult/feed", "why": "nav page" },
{ "url": "https://subscribestar.adult/messages", "why": "nav page" },
{ "url": "https://subscribestar.adult/library", "why": "nav page" },
{
"url": "https://subscribestar.adult/foobar/posts",
"why": "SubscribeStar's pattern end-anchors on the creator root, unlike Patreon's -- an inner page does NOT derive. Pinned so the asymmetry between the two platforms stays deliberate rather than becoming a silently-fixed bug in one copy only."
}
]
},
"hentaifoundry": {
"match": [
{
"url": "https://www.hentai-foundry.com/user/Foo",
"slug": "Foo",
"why": "user page"
},
{
"url": "https://www.hentai-foundry.com/user/Foo/profile",
"slug": "Foo",
"why": "inner page still derives the user"
},
{
"url": "https://hentai-foundry.com/user/Foo",
"slug": "Foo",
"why": "www is optional"
}
],
"no_match": [
{ "url": "https://www.hentai-foundry.com/pictures/popular", "why": "gallery listing, not a user" },
{ "url": "https://www.hentai-foundry.com/", "why": "site root" }
]
},
"discord": {
"match": [
{
"url": "https://discord.com/channels/111111111111111111/222222222222222222",
"slug": "111111111111111111/222222222222222222",
"why": "a channel: the slug is server/channel -- a Discord URL names a place, not a creator, so the artist is chosen in the Add panel"
},
{
"url": "https://discord.com/channels/111111111111111111",
"slug": "111111111111111111",
"why": "a whole server"
},
{
"url": "https://discord.com/channels/111111111111111111/222222222222222222/333333333333333333",
"slug": "111111111111111111/222222222222222222",
"why": "a message jump link still names its channel"
},
{
"url": "https://ptb.discord.com/channels/111111111111111111/222222222222222222",
"slug": "111111111111111111/222222222222222222",
"why": "the ptb and canary clients serve the same channels"
},
{
"url": "https://discord.com/channels/111111111111111111/222222222222222222/",
"slug": "111111111111111111/222222222222222222",
"why": "trailing slash is tolerated"
}
],
"no_match": [
{ "url": "https://discord.com/channels/@me", "why": "the DM list is not a source" },
{ "url": "https://discord.com/channels/@me/222222222222222222", "why": "a DM is not a source" },
{ "url": "https://discord.com/app", "why": "the app shell, no server open" },
{ "url": "https://discord.com/channels/111111111111111111/222222222222222222/threads/444444444444444444", "why": "thread links are left to the manual Add form" },
{ "url": "https://discord.com/servers/111111111111111111", "why": "a server-discovery page, not a channel" }
]
}
}