@@ -16,15 +16,21 @@ import getSinglePlaylistAndReturnVideoData, {
1616} from "./getSinglePlaylistAndReturnVideoData" ;
1717
1818/**
19- * Parse an ISO 8601 duration string (e.g. "PT1H2M3S") into total seconds.
19+ * Parse a full ISO 8601 duration string into total seconds.
20+ * Supports formats like "PT1H2M3S", "P1DT2H3M4S", "PT30S", etc.
21+ * Note: YouTube typically returns PT-prefixed durations, but very long
22+ * streams/videos could include a day component (P1DT...).
2023 */
2124function parseISO8601Duration ( duration : string ) : number {
22- const match = duration . match ( / P T (?: ( \d + ) H ) ? (?: ( \d + ) M ) ? (?: ( \d + ) S ) ? / ) ;
25+ const match = duration . match (
26+ / ^ P (?: ( \d + ) D ) ? (?: T (?: ( \d + ) H ) ? (?: ( \d + ) M ) ? (?: ( \d + ) S ) ? ) ? $ / ,
27+ ) ;
2328 if ( ! match ) return 0 ;
24- const hours = parseInt ( match [ 1 ] || "0" , 10 ) ;
25- const minutes = parseInt ( match [ 2 ] || "0" , 10 ) ;
26- const seconds = parseInt ( match [ 3 ] || "0" , 10 ) ;
27- return hours * 3600 + minutes * 60 + seconds ;
29+ const days = parseInt ( match [ 1 ] || "0" , 10 ) ;
30+ const hours = parseInt ( match [ 2 ] || "0" , 10 ) ;
31+ const minutes = parseInt ( match [ 3 ] || "0" , 10 ) ;
32+ const seconds = parseInt ( match [ 4 ] || "0" , 10 ) ;
33+ return days * 86400 + hours * 3600 + minutes * 60 + seconds ;
2834}
2935
3036/**
@@ -44,10 +50,20 @@ async function fetchVideoDuration(videoId: string): Promise<number> {
4450 return 0 ;
4551 }
4652
53+ // TODO: Type the response from YouTube API for better type safety
4754 const data = await res . json ( ) ;
4855 if ( ! data . items || data . items . length === 0 ) return 0 ;
4956
50- return parseISO8601Duration ( data . items [ 0 ] . contentDetails . duration ) ;
57+ const durationFirstItem = data . items [ 0 ] ;
58+ if ( ! durationFirstItem . contentDetails || ! durationFirstItem . contentDetails . duration ) {
59+ console . error ( "Duration not found in video details for video ID:" , videoId , ) ;
60+ return 0 ;
61+ }
62+
63+ const duration = durationFirstItem ?. contentDetails ?. duration ;
64+ if ( typeof duration !== "string" ) return 0 ;
65+
66+ return parseISO8601Duration ( duration ) ;
5167}
5268
5369export const updates = new Map <
@@ -135,14 +151,17 @@ export default async function fetchLatestUploads() {
135151 requiresUpdate ,
136152 ) ;
137153 // Use duration-based detection to reduce API quota usage
138- // and avoid UULF which is currently lagging
154+ // and avoid UULF which is currently lagging.
155+ // YouTube Shorts are currently limited to 3 minutes (180s).
156+ // Videos at exactly 180s could still be shorts, so we use
157+ // a strict greater-than check.
139158 const durationSeconds = await fetchVideoDuration ( videoId ) ;
140- const THREE_MINUTES = 180 ;
159+ const SHORTS_DURATION = 180 ;
141160
142161 let contentType : PlaylistType | null = null ;
143162
144- if ( durationSeconds >= THREE_MINUTES ) {
145- // Over 3 minutes : cannot be a short, check only if it's a stream
163+ if ( durationSeconds > SHORTS_DURATION ) {
164+ // Over the shorts limit : cannot be a short, check only if it's a stream
146165 const streamVideoId = await getSinglePlaylistAndReturnVideoData (
147166 channelId ,
148167 PlaylistType . Stream ,
0 commit comments