Handle rapid/bulk YouTube uploads by enqueueing all unseen uploads - #222
Draft
GalvinPython with Copilot wants to merge 3 commits into
Draft
Handle rapid/bulk YouTube uploads by enqueueing all unseen uploads#222GalvinPython with Copilot wants to merge 3 commits into
GalvinPython with Copilot wants to merge 3 commits into
Conversation
Copilot
AI
changed the title
[WIP] Fix missing notifications for rapid bulk YouTube uploads
Handle rapid/bulk YouTube uploads by enqueueing all unseen uploads
Jul 22, 2026
Comment on lines
+102
to
+105
| const pageTokenParam = pageToken ? `&pageToken=${pageToken}` : ""; | ||
| const res = await fetch( | ||
| `https://youtube.googleapis.com/youtube/v3/playlistItems?part=snippet&maxResults=50&playlistId=${uploadPlaylistId}&key=${env.youtubeApiKey}${pageTokenParam}`, | ||
| ); |
Owner
There was a problem hiding this comment.
we do not need to have page tokens imo - 50 videos is plenty enough to check
Comment on lines
+252
to
+367
| for (const videoId of videosToProcess) { | ||
| // Use duration-based detection to reduce API quota usage | ||
| // and avoid UULF which is currently lagging. | ||
| // YouTube Shorts are currently limited to 3 minutes (180s). | ||
| // Videos at exactly 180s could still be shorts, so we use | ||
| // a strict greater-than check. | ||
| const durationSeconds = await fetchVideoDuration(videoId); | ||
| const SHORTS_DURATION = 180; | ||
|
|
||
| let contentType: PlaylistType | null = null; | ||
|
|
||
| if (durationSeconds > SHORTS_DURATION) { | ||
| // Over the shorts limit: cannot be a short, check only if it's a stream | ||
| const streamVideoId = | ||
| await getSinglePlaylistAndReturnVideoData( | ||
| channelId, | ||
| PlaylistType.Stream, | ||
| ); | ||
|
|
||
| if (videoId === streamVideoId.videoId) { | ||
| contentType = PlaylistType.Stream; | ||
| } else { | ||
| // Not a stream and over 3 min; must be a regular video | ||
| contentType = PlaylistType.Video; | ||
| } | ||
| } else { | ||
| // Under 3 minutes: could be a short or a video, check UUSH and UULV | ||
| const [shortVideoId, streamVideoId] = await Promise.all([ | ||
| getSinglePlaylistAndReturnVideoData( | ||
| channelId, | ||
| PlaylistType.Short, | ||
| ), | ||
| getSinglePlaylistAndReturnVideoData( | ||
| channelId, | ||
| PlaylistType.Stream, | ||
| ), | ||
| ]); | ||
|
|
||
| if (videoId === shortVideoId.videoId) { | ||
| contentType = PlaylistType.Short; | ||
| } else if (videoId === streamVideoId.videoId) { | ||
| contentType = PlaylistType.Stream; | ||
| } else { | ||
| // Not in shorts or streams playlist → regular video | ||
| contentType = PlaylistType.Video; | ||
| } | ||
| } | ||
|
|
||
| console.log( | ||
| "Determined content type:", | ||
| contentType, | ||
| `(duration: ${durationSeconds}s)`, | ||
| ); | ||
|
|
||
| if (contentType) { | ||
| console.log( | ||
| `Updating ${contentType} video ID for channel`, | ||
| channelId, | ||
| "to", | ||
| videoId, | ||
| ); | ||
| } else { | ||
| console.error( | ||
| "No valid video ID found for channel", | ||
| channelId, | ||
| "with video ID", | ||
| videoId, | ||
| ); | ||
| continue; | ||
| } | ||
|
|
||
| const updateSuccess = await youtubeUpdateVideoId( | ||
| channelId, | ||
| videoId, | ||
| contentType, | ||
|
|
||
| // Temporarily using current date for update time | ||
| new Date(), | ||
| ); | ||
|
|
||
| if (!updateSuccess.success) { | ||
| console.error( | ||
| "Error updating video ID in fetchLatestUploads", | ||
| ); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| console.info(`Filtered guilds for channel ID ${channelId}:`, { | ||
| count: discordGuildsToUpdate.data.filter( | ||
| ( | ||
| guild, | ||
| ): guild is typeof dbGuildYouTubeSubscriptionsTable.$inferSelect => | ||
| "youtubeChannelId" in guild && | ||
| "trackVideos" in guild && | ||
| "trackShorts" in guild && | ||
| "trackStreams" in guild, | ||
| ).length, | ||
| }); | ||
|
|
||
| updates.set(videoId, { | ||
| channelInfo, | ||
| discordGuildsToUpdate: discordGuildsToUpdate.data.filter( | ||
| ( | ||
| guild, | ||
| ): guild is typeof dbGuildYouTubeSubscriptionsTable.$inferSelect => | ||
| "youtubeChannelId" in guild && | ||
| ((contentType === PlaylistType.Video && | ||
| guild.trackVideos) || | ||
| (contentType === PlaylistType.Short && | ||
| guild.trackShorts) || | ||
| (contentType === PlaylistType.Stream && | ||
| guild.trackStreams)), | ||
| ), | ||
| }); | ||
| } |
Owner
There was a problem hiding this comment.
this algorithm is checking the playlist for each video, however the playlist function will only return the latest video in that playlist. so if there's two shorts, it will be categorised as one video (default) and one short. we should check each playlist until each video id has been associated with a category to further reduce api calls
GalvinPython
self-requested a review
July 22, 2026 10:24
GalvinPython
requested changes
Jul 22, 2026
GalvinPython
left a comment
Owner
There was a problem hiding this comment.
@copilot make changes from comments
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Feedr was only comparing and processing the current head upload per channel, so when multiple videos were published between polling intervals, earlier items in the burst were skipped. This affected both regular uploads and Shorts.
Root cause
latestAllIddrift as a single-event update and only queued one video ID (the newest), dropping intermediate uploads.Behavior change
UU...): fetch playlist items until the storedlatestAllIdis reached (or pagination ends), collect all unseen IDs, then process them in chronological order (oldest → newest).Video/Short/Stream) and existing guild subscription filtering before enqueueing Discord notifications.Implementation notes
fetchUnseenUploadVideoIds(channelId, latestKnownVideoId)infetchLatestUploads.ts.