invidious-mod/src/invidious/playlists.cr

202 行
5.4 KiB
Crystal
Raw 通常表示 履歴

2018-09-29 13:12:35 +09:00
class PlaylistVideo
add_mapping({
title: String,
id: String,
author: String,
ucid: String,
length_seconds: Int32,
published: Time,
playlists: Array(String),
index: Int32,
})
end
2018-08-16 00:22:36 +09:00
class Playlist
add_mapping({
2018-09-05 09:27:10 +09:00
title: String,
id: String,
author: String,
author_thumbnail: String,
2018-09-05 09:27:10 +09:00
ucid: String,
description: String,
description_html: String,
video_count: Int32,
views: Int64,
updated: Time,
2018-08-16 00:22:36 +09:00
})
end
def fetch_playlist_videos(plid, page, video_count)
2018-08-16 00:22:36 +09:00
client = make_client(YT_URL)
if video_count > 100
index = (page - 1) * 100
url = produce_playlist_url(plid, index)
response = client.get(url)
response = JSON.parse(response.body)
if !response["content_html"]? || response["content_html"].as_s.empty?
raise "Playlist is empty"
end
document = XML.parse_html(response["content_html"].as_s)
nodeset = document.xpath_nodes(%q(.//tr[contains(@class, "pl-video")]))
videos = extract_playlist(plid, nodeset, index)
else
2018-09-28 23:54:01 +09:00
# Playlist has less than one page of videos, so subsequent pages will be empty
if page > 1
videos = [] of PlaylistVideo
else
2018-09-28 23:54:01 +09:00
# Extract first page of videos
2018-09-26 07:55:32 +09:00
response = client.get("/playlist?list=#{plid}&gl=US&hl=en&disable_polymer=1")
document = XML.parse_html(response.body)
nodeset = document.xpath_nodes(%q(.//tr[contains(@class, "pl-video")]))
videos = extract_playlist(plid, nodeset, 0)
end
2018-08-16 00:22:36 +09:00
end
return videos
end
def extract_playlist(plid, nodeset, index)
2018-08-16 00:22:36 +09:00
videos = [] of PlaylistVideo
nodeset.each_with_index do |video, offset|
anchor = video.xpath_node(%q(.//td[@class="pl-video-title"]))
if !anchor
next
2018-08-16 00:22:36 +09:00
end
title = anchor.xpath_node(%q(.//a)).not_nil!.content.strip(" \n")
id = anchor.xpath_node(%q(.//a)).not_nil!["href"].lchop("/watch?v=")[0, 11]
anchor = anchor.xpath_node(%q(.//div[@class="pl-video-owner"]/a))
if anchor
author = anchor.content
ucid = anchor["href"].split("/")[2]
else
author = ""
ucid = ""
end
anchor = video.xpath_node(%q(.//td[@class="pl-video-time"]/div/div[1]))
if anchor && !anchor.content.empty?
length_seconds = decode_length_seconds(anchor.content)
else
length_seconds = 0
end
videos << PlaylistVideo.new(
title,
id,
author,
ucid,
length_seconds,
Time.now,
[plid],
index + offset,
)
2018-08-16 00:22:36 +09:00
end
return videos
end
def produce_playlist_url(id, index)
if id.starts_with? "UC"
id = "UU" + id.lchop("UC")
end
ucid = "VL" + id
2018-09-28 23:54:01 +09:00
meta = [0x08_u8] + write_var_int(index)
meta = Slice.new(meta.to_unsafe, meta.size)
2018-09-18 06:38:18 +09:00
meta = Base64.urlsafe_encode(meta, false)
meta = "PT:#{meta}"
wrapped = "\x7a"
wrapped += meta.bytes.size.unsafe_chr
wrapped += meta
wrapped = Base64.urlsafe_encode(wrapped)
meta = URI.escape(wrapped)
continuation = "\x12"
continuation += ucid.size.unsafe_chr
continuation += ucid
continuation += "\x1a"
continuation += meta.bytes.size.unsafe_chr
continuation += meta
continuation = continuation.size.to_u8.unsafe_chr + continuation
continuation = "\xe2\xa9\x85\xb2\x02" + continuation
continuation = Base64.urlsafe_encode(continuation)
continuation = URI.escape(continuation)
2018-08-16 00:22:36 +09:00
url = "/browse_ajax?continuation=#{continuation}"
2018-08-16 00:22:36 +09:00
return url
end
def fetch_playlist(plid)
client = make_client(YT_URL)
if plid.starts_with? "UC"
plid = "UU#{plid.lchop("UC")}"
end
2018-09-26 07:55:32 +09:00
response = client.get("/playlist?list=#{plid}&hl=en&disable_polymer=1")
2018-09-24 02:26:12 +09:00
if response.status_code != 200
raise "Invalid playlist."
end
body = response.body.gsub(<<-END_BUTTON
<button class="yt-uix-button yt-uix-button-size-default yt-uix-button-link yt-uix-expander-head playlist-description-expander yt-uix-inlineedit-ignore-edit" type="button" onclick=";return false;"><span class="yt-uix-button-content"> less <img alt="" src="/yts/img/pixel-vfl3z5WfW.gif">
</span></button>
END_BUTTON
, "")
document = XML.parse_html(body)
2018-08-16 00:22:36 +09:00
2018-09-24 02:32:32 +09:00
title = document.xpath_node(%q(//h1[@class="pl-header-title"]))
if !title
raise "Playlist does not exist."
end
title = title.content.strip(" \n")
2018-08-16 00:22:36 +09:00
2018-09-05 09:27:10 +09:00
description_html = document.xpath_node(%q(//span[@class="pl-header-description-text"]/div/div[1]))
description_html ||= document.xpath_node(%q(//span[@class="pl-header-description-text"]))
description_html, description = html_to_content(description_html)
2018-08-16 00:22:36 +09:00
anchor = document.xpath_node(%q(//ul[@class="pl-header-details"])).not_nil!
author = anchor.xpath_node(%q(.//li[1]/a)).not_nil!.content
author_thumbnail = document.xpath_node(%q(//img[@class="channel-header-profile-image"])).try &.["src"]
author_thumbnail ||= ""
2018-08-16 00:22:36 +09:00
ucid = anchor.xpath_node(%q(.//li[1]/a)).not_nil!["href"].split("/")[2]
2018-08-21 00:25:05 +09:00
video_count = anchor.xpath_node(%q(.//li[2])).not_nil!.content.delete("videos, ").to_i
2018-09-18 09:21:13 +09:00
views = anchor.xpath_node(%q(.//li[3])).not_nil!.content.delete("No views, ")
if views.empty?
views = 0_i64
else
views = views.to_i64
end
2018-08-16 00:22:36 +09:00
updated = anchor.xpath_node(%q(.//li[4])).not_nil!.content.lchop("Last updated on ").lchop("Updated ")
updated = decode_date(updated)
playlist = Playlist.new(
title,
plid,
author,
author_thumbnail,
2018-08-16 00:22:36 +09:00
ucid,
description,
2018-09-05 09:27:10 +09:00
description_html,
2018-08-16 00:22:36 +09:00
video_count,
views,
updated
)
return playlist
end