struct MixVideo include DB::Serializable property title : String property id : String property author : String property ucid : String property length_seconds : Int32 property index : Int32 property rdid : String end struct Mix include DB::Serializable property title : String property id : String property videos : Array(MixVideo) end def fetch_mix(rdid, video_id, cookies = nil, locale = nil) headers = HTTP::Headers.new if cookies headers = cookies.add_request_headers(headers) end response = YT_POOL.client &.get("/watch?v=#{video_id}&list=#{rdid}&gl=US&hl=en&has_verified=1&bpctr=9999999999", headers) initial_data = extract_initial_data(response.body) if !initial_data["contents"]["twoColumnWatchNextResults"]["playlist"]? raise translate(locale, "Could not create mix.") end playlist = initial_data["contents"]["twoColumnWatchNextResults"]["playlist"]["playlist"] mix_title = playlist["title"].as_s contents = playlist["contents"].as_a if contents.map { |video| video["playlistPanelVideoRenderer"]["videoId"] }.includes? video_id until contents[0]["playlistPanelVideoRenderer"]["videoId"].as_s == video_id contents.shift end end videos = [] of MixVideo contents.each do |item| item = item["playlistPanelVideoRenderer"] id = item["videoId"].as_s title = item["title"]?.try &.["simpleText"].as_s next if !title author = item["longBylineText"]["runs"][0]["text"].as_s ucid = item["longBylineText"]["runs"][0]["navigationEndpoint"]["browseEndpoint"]["browseId"].as_s length_seconds = decode_length_seconds(item["lengthText"]["simpleText"].as_s) index = item["navigationEndpoint"]["watchEndpoint"]["index"].as_i videos << MixVideo.new({ title: title, id: id, author: author, ucid: ucid, length_seconds: length_seconds, index: index, rdid: rdid, }) end if !cookies next_page = fetch_mix(rdid, videos[-1].id, response.cookies, locale) videos += next_page.videos end videos.uniq! { |video| video.id } videos = videos.first(50) return Mix.new({ title: mix_title, id: rdid, videos: videos, }) end def template_mix(mix) html = <<-END_HTML