@@ -5,7 +5,6 @@ import { err, ok } from '@/server/lib/api-utils';
55const CACHE_TTL = 600 ;
66const CANONICAL_VIDEO_RE = / < l i n k r e l = " c a n o n i c a l " h r e f = " h t t p s : \/ \/ w w w \. y o u t u b e \. c o m \/ w a t c h \? v = ( [ ^ " & ] + ) " / ;
77const IS_LIVE_RE = / " i s L i v e " \s * : \s * t r u e / ;
8- const PLAYABLE_VIDEO_RE = / " p l a y a b i l i t y S t a t u s " : \{ " s t a t u s " : " O K " , " p l a y a b l e I n E m b e d " : ( t r u e | f a l s e ) [ \s \S ] { 0 , 4000 } ?" v i d e o I d " : " ( [ ^ " ] + ) " / ;
98
109type CacheEntry = {
1110 isLive : boolean ;
@@ -16,42 +15,36 @@ type CacheEntry = {
1615
1716const cache = new Map < string , CacheEntry > ( ) ;
1817
19- function escapeRegExp ( value : string ) {
20- return value . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) ;
21- }
22-
23- function parseLivePlayback ( html : string ) {
24- const canonicalVideoId = html . match ( CANONICAL_VIDEO_RE ) ?. [ 1 ] ?? null ;
25-
26- if ( canonicalVideoId ) {
27- const canonicalPlayable = new RegExp (
28- `"playabilityStatus":\\{"status":"OK","playableInEmbed":(true|false)[\\s\\S]{0,4000}?"videoId":"${ escapeRegExp ( canonicalVideoId ) } "` ,
29- ) . exec ( html ) ;
18+ async function isEmbeddableVideo ( videoId : string ) {
19+ const oembedUrl = `https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=${ videoId } &format=json` ;
3020
31- if ( canonicalPlayable ) {
32- return {
33- playableInEmbed : canonicalPlayable [ 1 ] === 'true' ,
34- videoId : canonicalPlayable [ 1 ] === 'true' ? canonicalVideoId : null ,
35- } ;
36- }
21+ try {
22+ const res = await fetch ( oembedUrl , {
23+ headers : { 'User-Agent' : 'Mozilla/5.0' } ,
24+ signal : AbortSignal . timeout ( 8000 ) ,
25+ } ) ;
3726
38- return {
39- playableInEmbed : true ,
40- videoId : canonicalVideoId ,
41- } ;
27+ return res . ok ;
28+ } catch {
29+ return false ;
4230 }
31+ }
4332
44- const playableMatch = html . match ( PLAYABLE_VIDEO_RE ) ;
45- if ( ! playableMatch ) {
33+ async function parseLivePlayback ( html : string ) {
34+ const canonicalVideoId = html . match ( CANONICAL_VIDEO_RE ) ?. [ 1 ] ?? null ;
35+
36+ if ( ! canonicalVideoId ) {
4637 return {
4738 playableInEmbed : false ,
4839 videoId : null ,
4940 } ;
5041 }
5142
43+ const playableInEmbed = await isEmbeddableVideo ( canonicalVideoId ) ;
44+
5245 return {
53- playableInEmbed : playableMatch [ 1 ] === 'true' ,
54- videoId : playableMatch [ 1 ] === 'true' ? playableMatch [ 2 ] : null ,
46+ playableInEmbed,
47+ videoId : playableInEmbed ? canonicalVideoId : null ,
5548 } ;
5649}
5750
@@ -73,7 +66,7 @@ async function checkLiveStatus(handle: string): Promise<{ isLive: boolean; playa
7366 const html = await res . text ( ) ;
7467 const isLive = IS_LIVE_RE . test ( html ) ;
7568 const playback = isLive
76- ? parseLivePlayback ( html )
69+ ? await parseLivePlayback ( html )
7770 : { playableInEmbed : false , videoId : null } ;
7871 const playableInEmbed = playback . playableInEmbed ;
7972 const videoId = playback . videoId ;
0 commit comments