* Enqueue a follow-up URL-list job for videos that failed with a transient * HTTP 403. Owning channels are resolved so channel-tier settings (quality, * audio format, subfolder routing) apply on the retry; the source job's * overrideSettings carry dialog-picked options through for manual an
({ retryVideos, autoRetryAttempt, runId, sourceJobData = {} })
| 112 | * @param {Object} params.sourceJobData - the failing job's data payload |
| 113 | */ |
| 114 | async enqueueAutoRetryJob({ retryVideos, autoRetryAttempt, runId, sourceJobData = {} }) { |
| 115 | if (!Array.isArray(retryVideos) || retryVideos.length === 0) return; |
| 116 | |
| 117 | const ownerChannelMap = { ...(this.getJobDataValue(sourceJobData, 'ownerChannelMap') || {}) }; |
| 118 | const unmappedIds = retryVideos |
| 119 | .map((video) => video.youtubeId) |
| 120 | .filter((id) => id && !ownerChannelMap[id]); |
| 121 | if (unmappedIds.length > 0) { |
| 122 | try { |
| 123 | const rows = await ChannelVideo.findAll({ |
| 124 | where: { youtube_id: unmappedIds }, |
| 125 | attributes: ['youtube_id', 'channel_id'], |
| 126 | }); |
| 127 | for (const row of rows) { |
| 128 | if (row.channel_id) ownerChannelMap[row.youtube_id] = row.channel_id; |
| 129 | } |
| 130 | } catch (err) { |
| 131 | logger.warn({ err }, 'Could not resolve owning channels for auto-retry; falling back to global settings'); |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | // A single owning channel lets doSpecificDownloads resolve channel-tier |
| 136 | // quality/audio/skip-video-folder; with mixed channels those fall back to |
| 137 | // the copied override or global settings (subfolder routing still resolves |
| 138 | // per video via ownerChannelMap in post-processing). |
| 139 | const mappedChannelIds = new Set( |
| 140 | retryVideos.map((video) => ownerChannelMap[video.youtubeId]).filter(Boolean) |
| 141 | ); |
| 142 | const channelId = this.getJobDataValue(sourceJobData, 'channelId') |
| 143 | || (mappedChannelIds.size === 1 ? [...mappedChannelIds][0] : null); |
| 144 | const effectiveQuality = this.getJobDataValue(sourceJobData, 'effectiveQuality') || null; |
| 145 | |
| 146 | const body = { |
| 147 | urls: retryVideos.map((video) => video.url), |
| 148 | overrideSettings: { ...this.getOverrideSettings(sourceJobData) }, |
| 149 | jobLabel: autoRetryJobLabel(retryVideos.length), |
| 150 | autoRetryAttempt, |
| 151 | }; |
| 152 | if (Object.keys(ownerChannelMap).length > 0) body.ownerChannelMap = ownerChannelMap; |
| 153 | if (channelId) body.channelId = channelId; |
| 154 | if (effectiveQuality) body.effectiveQuality = effectiveQuality; |
| 155 | if (runId) body.runId = runId; |
| 156 | |
| 157 | logger.info( |
| 158 | { videoCount: retryVideos.length, autoRetryAttempt, channelId, runId }, |
| 159 | 'Enqueueing auto-retry job for transient 403 failures' |
| 160 | ); |
| 161 | await this.doSpecificDownloads({ body }); |
| 162 | } |
| 163 | |
| 164 | async doChannelDownloads(jobData = {}, isNextJob = false) { |
| 165 | const overrideSettings = this.getOverrideSettings(jobData); |
no test coverage detected