feat: the extension adds Discord channels to an artist you pick, and its tests gate the XPI (milestone 429)
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
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
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
This commit is contained in:
@@ -7,10 +7,14 @@ import { loadLib } from './helpers/loadLib.js'
|
||||
const EXT_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const manifest = JSON.parse(readFileSync(path.join(EXT_DIR, 'manifest.json'), 'utf8'))
|
||||
|
||||
const { getPlatformFromUrl, isArtistPage, PLATFORMS, PLATFORM_ARTIST_PATTERNS } = loadLib(
|
||||
'platforms.js',
|
||||
['getPlatformFromUrl', 'isArtistPage', 'PLATFORMS', 'PLATFORM_ARTIST_PATTERNS']
|
||||
)
|
||||
const { getPlatformFromUrl, isArtistPage, parseDiscordUrl, PLATFORMS, PLATFORM_ARTIST_PATTERNS } =
|
||||
loadLib('platforms.js', [
|
||||
'getPlatformFromUrl',
|
||||
'isArtistPage',
|
||||
'parseDiscordUrl',
|
||||
'PLATFORMS',
|
||||
'PLATFORM_ARTIST_PATTERNS'
|
||||
])
|
||||
|
||||
describe('getPlatformFromUrl', () => {
|
||||
it('identifies each platform from a domain URL', () => {
|
||||
@@ -88,8 +92,11 @@ describe('isArtistPage', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('returns false for a platform with no artist pattern (discord)', () => {
|
||||
it('matches Discord server and channel pages, not DMs (milestone 429)', () => {
|
||||
expect(isArtistPage('https://discord.com/channels/111/222', 'discord')).toBe(true)
|
||||
expect(isArtistPage('https://discord.com/channels/111', 'discord')).toBe(true)
|
||||
expect(isArtistPage('https://discord.com/channels/@me', 'discord')).toBe(false)
|
||||
expect(isArtistPage('https://discord.com/channels/@me/222', 'discord')).toBe(false)
|
||||
})
|
||||
|
||||
it('returns false for an unknown platform key', () => {
|
||||
@@ -100,8 +107,8 @@ describe('isArtistPage', () => {
|
||||
describe('platform table integrity', () => {
|
||||
it('gives every artist pattern a corresponding platform entry', () => {
|
||||
// A pattern keyed to a platform that no longer exists is dead code that
|
||||
// silently never fires; the reverse (a platform with no pattern) is the
|
||||
// legitimate discord case, so only this direction is an error.
|
||||
// silently never fires; the reverse (a platform with no pattern) would be
|
||||
// a platform the button never offers, a product choice rather than an error.
|
||||
for (const key of Object.keys(PLATFORM_ARTIST_PATTERNS)) {
|
||||
expect(Object.keys(PLATFORMS)).toContain(key)
|
||||
}
|
||||
@@ -125,7 +132,8 @@ describe('platform table integrity', () => {
|
||||
const samples = {
|
||||
patreon: 'https://www.patreon.com/cw/Atole',
|
||||
subscribestar: 'https://subscribestar.adult/someone',
|
||||
hentaifoundry: 'https://www.hentai-foundry.com/user/someone'
|
||||
hentaifoundry: 'https://www.hentai-foundry.com/user/someone',
|
||||
discord: 'https://ptb.discord.com/channels/111/222'
|
||||
}
|
||||
for (const [key, url] of Object.entries(samples)) {
|
||||
expect(isArtistPage(url, key), `${key} artist pattern`).toBe(true)
|
||||
@@ -152,7 +160,7 @@ describe('manifest.json agrees with the platform table', () => {
|
||||
)
|
||||
expect(owner, `no platform claims content-script match "${m}"`).toBeTruthy()
|
||||
// The content script exists to draw the Add-as-source button, so a
|
||||
// platform with no artist pattern (discord) has no business here.
|
||||
// platform with no artist pattern has no business here.
|
||||
expect(
|
||||
PLATFORM_ARTIST_PATTERNS[owner[0]],
|
||||
`"${m}" injects for ${owner[0]}, which has no artist pattern`
|
||||
@@ -232,9 +240,8 @@ describe('the JS<->Py artist-pattern mirror (#3093)', () => {
|
||||
|
||||
it('has samples for every platform that has an artist pattern', () => {
|
||||
// The guard's own coverage check: without it, deleting a platform's
|
||||
// samples would make this block pass by testing less. Discord is
|
||||
// deliberately in neither — it is channel-based, with no creator page to
|
||||
// put a button on, so it has no artist pattern on either side.
|
||||
// samples would make this block pass by testing less. Discord joined at
|
||||
// milestone 429 — its slug is server/channel and the artist is chosen.
|
||||
expect(Object.keys(samples).sort()).toEqual(Object.keys(PLATFORM_ARTIST_PATTERNS).sort())
|
||||
})
|
||||
|
||||
@@ -247,3 +254,26 @@ describe('the JS<->Py artist-pattern mirror (#3093)', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseDiscordUrl', () => {
|
||||
it('reads the server and channel ids the Add panel offers', () => {
|
||||
expect(parseDiscordUrl('https://discord.com/channels/111/222')).toEqual({
|
||||
serverId: '111',
|
||||
channelId: '222'
|
||||
})
|
||||
expect(parseDiscordUrl('https://discord.com/channels/111/222/333')).toEqual({
|
||||
serverId: '111',
|
||||
channelId: '222'
|
||||
})
|
||||
expect(parseDiscordUrl('https://discord.com/channels/111')).toEqual({
|
||||
serverId: '111',
|
||||
channelId: null
|
||||
})
|
||||
})
|
||||
|
||||
it('returns null for anything the artist pattern rejects', () => {
|
||||
expect(parseDiscordUrl('https://discord.com/channels/@me/222')).toBe(null)
|
||||
expect(parseDiscordUrl('https://discord.com/app')).toBe(null)
|
||||
expect(parseDiscordUrl('')).toBe(null)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user