feat(embeds): Vimeo live events play inline (#200)
CI / Build & Quality Checks (pull_request) Successful in 4m38s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 17s
CI / Playwright smoke (e2e) (pull_request) Successful in 24m27s

vimeo.com/event/{id} (and /event/{id}/{hash} for unlisted events) now get
the same click-to-play card as Vimeo videos, embedding Vimeo's live-event
player (vimeo.com/event/{id}/embed[/{hash}], dnt=1). The issue assumed
this needed vimeo.com in the CSP, but frame-src is already https: on web
and desktop.

Refs #200

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
This commit is contained in:
Lotus CI
2026-10-20 23:31:13 -04:00
co-authored by Claude Opus 5.5
parent 878a34712a
commit ed5bf997b4
2 changed files with 59 additions and 0 deletions
+32
View File
@@ -33,6 +33,7 @@ import {
spotifyEmbedHeight,
parseMediaEmbed,
isTwitterNoResults,
getVimeoEventParts,
} from './videoEmbed';
const HOST = 'chat.lotusguild.org';
@@ -429,3 +430,34 @@ test('isTwitterNoResults: ignores normal embed traffic and junk', () => {
assert.equal(isTwitterNoResults(null), false);
assert.equal(isTwitterNoResults({ type: 'MEASURE' }), false);
});
test('[#200] Vimeo live events embed from vimeo.com/event/{id}/embed', () => {
assert.deepEqual(getVimeoEventParts('https://vimeo.com/event/4321567'), {
id: '4321567',
hash: undefined,
});
assert.deepEqual(getVimeoEventParts('https://vimeo.com/event/4321567/a1b2c3d4e5'), {
id: '4321567',
hash: 'a1b2c3d4e5',
});
assert.equal(
getVimeoEventParts('https://vimeo.com/event/4321567/embed/a1b2c3d4e5')?.hash,
'a1b2c3d4e5',
);
assert.equal(getVimeoEventParts('https://vimeo.com/event/4321567/chat'), null);
assert.equal(getVimeoEventParts('https://vimeo.com/123456789'), null);
assert.equal(getVimeoEventParts('https://evil.example/event/4321567'), null);
assert.equal(
parseMediaEmbed('https://vimeo.com/event/4321567', 'h')?.embedUrl,
'https://vimeo.com/event/4321567/embed?autoplay=1&dnt=1',
);
assert.equal(
parseMediaEmbed('https://vimeo.com/event/4321567/a1b2c3d4e5', 'h')?.embedUrl,
'https://vimeo.com/event/4321567/embed/a1b2c3d4e5?autoplay=1&dnt=1',
);
// An ordinary video still goes to the player.
assert.match(
parseMediaEmbed('https://vimeo.com/123456789', 'h')?.embedUrl ?? '',
/^https:\/\/player\.vimeo\.com\/video\/123456789/,
);
});
+27
View File
@@ -80,6 +80,25 @@ export function getVimeoParts(url: string): { id: string; hash?: string } | null
}
}
/**
* [Gitea #200] Vimeo live events: vimeo.com/event/{id}, or /event/{id}/{hash}
* when unlisted. Embedded from vimeo.com itself (not player.vimeo.com); the
* web and desktop CSP `frame-src https:` already allow it.
*/
export function getVimeoEventParts(url: string): { id: string; hash?: string } | null {
try {
const { hostname, pathname } = new URL(url);
if (hostname !== 'vimeo.com' && hostname !== 'www.vimeo.com') return null;
const m = pathname.match(/^\/event\/(\d+)(?:\/(?:embed\/)?([0-9a-f]{6,}))?\/?$/);
return m ? { id: m[1], hash: m[2] } : null;
} catch {
return null;
}
}
export const buildVimeoEventEmbedUrl = (id: string, hash?: string): string =>
`https://vimeo.com/event/${enc(id)}/embed${hash ? `/${enc(hash)}` : ''}?autoplay=1&dnt=1`;
export function getVimeoVideoId(url: string): string | null {
return getVimeoParts(url)?.id ?? null;
}
@@ -713,6 +732,14 @@ export function parseMediaEmbed(url: string, host: string): MediaEmbed | null {
embedUrl: buildVideoEmbedUrl('youtube', ytId),
};
const vimeoEvent = getVimeoEventParts(url);
if (vimeoEvent)
return {
provider: 'vimeo',
kind: 'landscape',
embedUrl: buildVimeoEventEmbedUrl(vimeoEvent.id, vimeoEvent.hash),
};
const vimeo = getVimeoParts(url);
if (vimeo)
return {