summaryrefslogtreecommitdiffstats
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/invidious/helpers_spec.cr14
-rw-r--r--spec/invidious/search/iv_filters_spec.cr371
-rw-r--r--spec/invidious/search/yt_filters_spec.cr143
-rw-r--r--spec/spec_helper.cr2
4 files changed, 515 insertions, 15 deletions
diff --git a/spec/invidious/helpers_spec.cr b/spec/invidious/helpers_spec.cr
index b2436989..5ecebef3 100644
--- a/spec/invidious/helpers_spec.cr
+++ b/spec/invidious/helpers_spec.cr
@@ -29,20 +29,6 @@ Spectator.describe "Helper" do
end
end
- describe "#produce_search_params" do
- it "correctly produces token for searching with specified filters" do
- expect(produce_search_params).to eq("CAASAhABSAA%3D")
-
- expect(produce_search_params(sort: "upload_date", content_type: "video")).to eq("CAISAhABSAA%3D")
-
- expect(produce_search_params(content_type: "playlist")).to eq("CAASAhADSAA%3D")
-
- expect(produce_search_params(sort: "date", content_type: "video", features: ["hd", "cc", "purchased", "hdr"])).to eq("CAISCxABIAEwAUgByAEBSAA%3D")
-
- expect(produce_search_params(content_type: "channel")).to eq("CAASAhACSAA%3D")
- end
- end
-
describe "#produce_comment_continuation" do
it "correctly produces a continuation token for comments" do
expect(produce_comment_continuation("_cE8xSu6swE", "ADSJ_i2qvJeFtL0htmS5_K5Ctj3eGFVBMWL9Wd42o3kmUL6_mAzdLp85-liQZL0mYr_16BhaggUqX652Sv9JqV6VXinShSP-ZT6rL4NolPBaPXVtJsO5_rA_qE3GubAuLFw9uzIIXU2-HnpXbdgPLWTFavfX206hqWmmpHwUOrmxQV_OX6tYkM3ux3rPAKCDrT8eWL7MU3bLiNcnbgkW8o0h8KYLL_8BPa8LcHbTv8pAoNkjerlX1x7K4pqxaXPoyz89qNlnh6rRx6AXgAzzoHH1dmcyQ8CIBeOHg-m4i8ZxdX4dP88XWrIFg-jJGhpGP8JUMDgZgavxVx225hUEYZMyrLGler5em4FgbG62YWC51moLDLeYEA")).to eq("EkMSC19jRTh4U3U2c3dFyAEA4AEBogINKP___________wFAAMICHQgEGhdodHRwczovL3d3dy55b3V0dWJlLmNvbSIAGAYyjAMK9gJBRFNKX2kycXZKZUZ0TDBodG1TNV9LNUN0ajNlR0ZWQk1XTDlXZDQybzNrbVVMNl9tQXpkTHA4NS1saVFaTDBtWXJfMTZCaGFnZ1VxWDY1MlN2OUpxVjZWWGluU2hTUC1aVDZyTDROb2xQQmFQWFZ0SnNPNV9yQV9xRTNHdWJBdUxGdzl1eklJWFUyLUhucFhiZGdQTFdURmF2ZlgyMDZocVdtbXBId1VPcm14UVZfT1g2dFlrTTN1eDNyUEFLQ0RyVDhlV0w3TVUzYkxpTmNuYmdrVzhvMGg4S1lMTF84QlBhOExjSGJUdjhwQW9Oa2plcmxYMXg3SzRwcXhhWFBveXo4OXFObG5oNnJSeDZBWGdBenpvSEgxZG1jeVE4Q0lCZU9IZy1tNGk4WnhkWDRkUDg4WFdySUZnLWpKR2hwR1A4SlVNRGdaZ2F2eFZ4MjI1aFVFWVpNeXJMR2xlcjVlbTRGZ2JHNjJZV0M1MW1vTERMZVlFQSIPIgtfY0U4eFN1NnN3RTAAKBQ%3D")
diff --git a/spec/invidious/search/iv_filters_spec.cr b/spec/invidious/search/iv_filters_spec.cr
new file mode 100644
index 00000000..b0897a63
--- /dev/null
+++ b/spec/invidious/search/iv_filters_spec.cr
@@ -0,0 +1,371 @@
+require "../../../src/invidious/search/filters"
+
+require "http/params"
+require "spectator"
+
+Spectator.configure do |config|
+ config.fail_blank
+ config.randomize
+end
+
+FEATURES_TEXT = {
+ Invidious::Search::Filters::Features::Live => "live",
+ Invidious::Search::Filters::Features::FourK => "4k",
+ Invidious::Search::Filters::Features::HD => "hd",
+ Invidious::Search::Filters::Features::Subtitles => "subtitles",
+ Invidious::Search::Filters::Features::CCommons => "commons",
+ Invidious::Search::Filters::Features::ThreeSixty => "360",
+ Invidious::Search::Filters::Features::VR180 => "vr180",
+ Invidious::Search::Filters::Features::ThreeD => "3d",
+ Invidious::Search::Filters::Features::HDR => "hdr",
+ Invidious::Search::Filters::Features::Location => "location",
+ Invidious::Search::Filters::Features::Purchased => "purchased",
+}
+
+Spectator.describe Invidious::Search::Filters do
+ # -------------------
+ # Decode (legacy)
+ # -------------------
+
+ describe "#from_legacy_filters" do
+ it "Decodes channel: filter" do
+ query = "test channel:UC123456 request"
+
+ fltr, chan, qury, subs = described_class.from_legacy_filters(query)
+
+ expect(fltr).to eq(described_class.new)
+ expect(chan).to eq("UC123456")
+ expect(qury).to eq("test request")
+ expect(subs).to be_false
+ end
+
+ it "Decodes user: filter" do
+ query = "user:LinusTechTips broke something (again)"
+
+ fltr, chan, qury, subs = described_class.from_legacy_filters(query)
+
+ expect(fltr).to eq(described_class.new)
+ expect(chan).to eq("LinusTechTips")
+ expect(qury).to eq("broke something (again)")
+ expect(subs).to be_false
+ end
+
+ it "Decodes type: filter" do
+ Invidious::Search::Filters::Type.each do |value|
+ query = "Eiffel 65 - Blue [1 Hour] type:#{value}"
+
+ fltr, chan, qury, subs = described_class.from_legacy_filters(query)
+
+ expect(fltr).to eq(described_class.new(type: value))
+ expect(chan).to eq("")
+ expect(qury).to eq("Eiffel 65 - Blue [1 Hour]")
+ expect(subs).to be_false
+ end
+ end
+
+ it "Decodes content_type: filter" do
+ Invidious::Search::Filters::Type.each do |value|
+ query = "I like to watch content_type:#{value}"
+
+ fltr, chan, qury, subs = described_class.from_legacy_filters(query)
+
+ expect(fltr).to eq(described_class.new(type: value))
+ expect(chan).to eq("")
+ expect(qury).to eq("I like to watch")
+ expect(subs).to be_false
+ end
+ end
+
+ it "Decodes date: filter" do
+ Invidious::Search::Filters::Date.each do |value|
+ query = "This date:#{value} is old!"
+
+ fltr, chan, qury, subs = described_class.from_legacy_filters(query)
+
+ expect(fltr).to eq(described_class.new(date: value))
+ expect(chan).to eq("")
+ expect(qury).to eq("This is old!")
+ expect(subs).to be_false
+ end
+ end
+
+ it "Decodes duration: filter" do
+ Invidious::Search::Filters::Duration.each do |value|
+ query = "This duration:#{value} is old!"
+
+ fltr, chan, qury, subs = described_class.from_legacy_filters(query)
+
+ expect(fltr).to eq(described_class.new(duration: value))
+ expect(chan).to eq("")
+ expect(qury).to eq("This is old!")
+ expect(subs).to be_false
+ end
+ end
+
+ it "Decodes feature: filter" do
+ Invidious::Search::Filters::Features.each do |value|
+ string = FEATURES_TEXT[value]
+ query = "I like my precious feature:#{string} ^^"
+
+ fltr, chan, qury, subs = described_class.from_legacy_filters(query)
+
+ expect(fltr).to eq(described_class.new(features: value))
+ expect(chan).to eq("")
+ expect(qury).to eq("I like my precious ^^")
+ expect(subs).to be_false
+ end
+ end
+
+ it "Decodes features: filter" do
+ query = "This search has many features:vr180,cc,hdr :o"
+
+ fltr, chan, qury, subs = described_class.from_legacy_filters(query)
+
+ features = Invidious::Search::Filters::Features.flags(HDR, VR180, CCommons)
+
+ expect(fltr).to eq(described_class.new(features: features))
+ expect(chan).to eq("")
+ expect(qury).to eq("This search has many :o")
+ expect(subs).to be_false
+ end
+
+ it "Decodes sort: filter" do
+ Invidious::Search::Filters::Sort.each do |value|
+ query = "Computer? sort:#{value} my files!"
+
+ fltr, chan, qury, subs = described_class.from_legacy_filters(query)
+
+ expect(fltr).to eq(described_class.new(sort: value))
+ expect(chan).to eq("")
+ expect(qury).to eq("Computer? my files!")
+ expect(subs).to be_false
+ end
+ end
+
+ it "Decodes subscriptions: filter" do
+ query = "enable subscriptions:true"
+
+ fltr, chan, qury, subs = described_class.from_legacy_filters(query)
+
+ expect(fltr).to eq(described_class.new)
+ expect(chan).to eq("")
+ expect(qury).to eq("enable")
+ expect(subs).to be_true
+ end
+
+ it "Ignores junk data" do
+ query = "duration:I sort:like type:cleaning features:stuff date:up!"
+
+ fltr, chan, qury, subs = described_class.from_legacy_filters(query)
+
+ expect(fltr).to eq(described_class.new)
+ expect(chan).to eq("")
+ expect(qury).to eq("")
+ expect(subs).to be_false
+ end
+
+ it "Keeps unknown keys" do
+ query = "to:be or:not to:be"
+
+ fltr, chan, qury, subs = described_class.from_legacy_filters(query)
+
+ expect(fltr).to eq(described_class.new)
+ expect(chan).to eq("")
+ expect(qury).to eq("to:be or:not to:be")
+ expect(subs).to be_false
+ end
+ end
+
+ # -------------------
+ # Decode (URL)
+ # -------------------
+
+ describe "#from_iv_params" do
+ it "Decodes type= filter" do
+ Invidious::Search::Filters::Type.each do |value|
+ params = HTTP::Params.parse("type=#{value}")
+
+ expect(described_class.from_iv_params(params))
+ .to eq(described_class.new(type: value))
+ end
+ end
+
+ it "Decodes date= filter" do
+ Invidious::Search::Filters::Date.each do |value|
+ params = HTTP::Params.parse("date=#{value}")
+
+ expect(described_class.from_iv_params(params))
+ .to eq(described_class.new(date: value))
+ end
+ end
+
+ it "Decodes duration= filter" do
+ Invidious::Search::Filters::Duration.each do |value|
+ params = HTTP::Params.parse("duration=#{value}")
+
+ expect(described_class.from_iv_params(params))
+ .to eq(described_class.new(duration: value))
+ end
+ end
+
+ it "Decodes features= filter (single)" do
+ Invidious::Search::Filters::Features.each do |value|
+ string = described_class.format_features(value)
+ params = HTTP::Params.parse("features=#{string}")
+
+ expect(described_class.from_iv_params(params))
+ .to eq(described_class.new(features: value))
+ end
+ end
+
+ it "Decodes features= filter (multiple - comma separated)" do
+ features = Invidious::Search::Filters::Features.flags(HDR, VR180, CCommons)
+ params = HTTP::Params.parse("features=vr180%2Ccc%2Chdr") # %2C is a comma
+
+ expect(described_class.from_iv_params(params))
+ .to eq(described_class.new(features: features))
+ end
+
+ it "Decodes features= filter (multiple - URL parameters)" do
+ features = Invidious::Search::Filters::Features.flags(ThreeSixty, HD, FourK)
+ params = HTTP::Params.parse("features=4k&features=360&features=hd")
+
+ expect(described_class.from_iv_params(params))
+ .to eq(described_class.new(features: features))
+ end
+
+ it "Decodes sort= filter" do
+ Invidious::Search::Filters::Sort.each do |value|
+ params = HTTP::Params.parse("sort=#{value}")
+
+ expect(described_class.from_iv_params(params))
+ .to eq(described_class.new(sort: value))
+ end
+ end
+
+ it "Ignores junk data" do
+ params = HTTP::Params.parse("foo=bar&sort=views&answer=42&type=channel")
+
+ expect(described_class.from_iv_params(params)).to eq(
+ described_class.new(
+ sort: Invidious::Search::Filters::Sort::Views,
+ type: Invidious::Search::Filters::Type::Channel
+ )
+ )
+ end
+ end
+
+ # -------------------
+ # Encode (URL)
+ # -------------------
+
+ describe "#to_iv_params" do
+ it "Encodes date filter" do
+ Invidious::Search::Filters::Date.each do |value|
+ filters = described_class.new(date: value)
+ params = filters.to_iv_params
+
+ if value.none?
+ expect("#{params}").to eq("")
+ else
+ expect("#{params}").to eq("date=#{value.to_s.underscore}")
+ end
+ end
+ end
+
+ it "Encodes type filter" do
+ Invidious::Search::Filters::Type.each do |value|
+ filters = described_class.new(type: value)
+ params = filters.to_iv_params
+
+ if value.all?
+ expect("#{params}").to eq("")
+ else
+ expect("#{params}").to eq("type=#{value.to_s.underscore}")
+ end
+ end
+ end
+
+ it "Encodes duration filter" do
+ Invidious::Search::Filters::Duration.each do |value|
+ filters = described_class.new(duration: value)
+ params = filters.to_iv_params
+
+ if value.none?
+ expect("#{params}").to eq("")
+ else
+ expect("#{params}").to eq("duration=#{value.to_s.underscore}")
+ end
+ end
+ end
+
+ it "Encodes features filter (single)" do
+ Invidious::Search::Filters::Features.each do |value|
+ string = described_class.format_features(value)
+ filters = described_class.new(features: value)
+
+ expect("#{filters.to_iv_params}")
+ .to eq("features=" + FEATURES_TEXT[value])
+ end
+ end
+
+ it "Encodes features filter (multiple)" do
+ features = Invidious::Search::Filters::Features.flags(Subtitles, Live, ThreeSixty)
+ filters = described_class.new(features: features)
+
+ expect("#{filters.to_iv_params}")
+ .to eq("features=live%2Csubtitles%2C360") # %2C is a comma
+ end
+
+ it "Encodes sort filter" do
+ Invidious::Search::Filters::Sort.each do |value|
+ filters = described_class.new(sort: value)
+ params = filters.to_iv_params
+
+ if value.relevance?
+ expect("#{params}").to eq("")
+ else
+ expect("#{params}").to eq("sort=#{value.to_s.underscore}")
+ end
+ end
+ end
+
+ it "Encodes multiple filters" do
+ filters = described_class.new(
+ date: Invidious::Search::Filters::Date::Today,
+ duration: Invidious::Search::Filters::Duration::Medium,
+ features: Invidious::Search::Filters::Features.flags(Location, Purchased),
+ sort: Invidious::Search::Filters::Sort::Relevance
+ )
+
+ params = filters.to_iv_params
+
+ # Check the `date` param
+ expect(params).to have_key("date")
+ expect(params.fetch_all("date")).to contain_exactly("today")
+
+ # Check the `type` param
+ expect(params).to_not have_key("type")
+ expect(params["type"]?).to be_nil
+
+ # Check the `duration` param
+ expect(params).to have_key("duration")
+ expect(params.fetch_all("duration")).to contain_exactly("medium")
+
+ # Check the `features` param
+ expect(params).to have_key("features")
+ expect(params.fetch_all("features")).to contain_exactly("location,purchased")
+
+ # Check the `sort` param
+ expect(params).to_not have_key("sort")
+ expect(params["sort"]?).to be_nil
+
+ # Check if there aren't other parameters
+ params.delete("date")
+ params.delete("duration")
+ params.delete("features")
+
+ expect(params).to be_empty
+ end
+ end
+end
diff --git a/spec/invidious/search/yt_filters_spec.cr b/spec/invidious/search/yt_filters_spec.cr
new file mode 100644
index 00000000..bf7f21e7
--- /dev/null
+++ b/spec/invidious/search/yt_filters_spec.cr
@@ -0,0 +1,143 @@
+require "../../../src/invidious/search/filters"
+
+require "http/params"
+require "spectator"
+
+Spectator.configure do |config|
+ config.fail_blank
+ config.randomize
+end
+
+# Encoded filter values are extracted from the search
+# page of Youtube with any browser devtools HTML inspector.
+
+DATE_FILTERS = {
+ Invidious::Search::Filters::Date::Hour => "EgIIAQ%3D%3D",
+ Invidious::Search::Filters::Date::Today => "EgIIAg%3D%3D",
+ Invidious::Search::Filters::Date::Week => "EgIIAw%3D%3D",
+ Invidious::Search::Filters::Date::Month => "EgIIBA%3D%3D",
+ Invidious::Search::Filters::Date::Year => "EgIIBQ%3D%3D",
+}
+
+TYPE_FILTERS = {
+ Invidious::Search::Filters::Type::Video => "EgIQAQ%3D%3D",
+ Invidious::Search::Filters::Type::Channel => "EgIQAg%3D%3D",
+ Invidious::Search::Filters::Type::Playlist => "EgIQAw%3D%3D",
+ Invidious::Search::Filters::Type::Movie => "EgIQBA%3D%3D",
+}
+
+DURATION_FILTERS = {
+ Invidious::Search::Filters::Duration::Short => "EgIYAQ%3D%3D",
+ Invidious::Search::Filters::Duration::Medium => "EgIYAw%3D%3D",
+ Invidious::Search::Filters::Duration::Long => "EgIYAg%3D%3D",
+}
+
+FEATURE_FILTERS = {
+ Invidious::Search::Filters::Features::Live => "EgJAAQ%3D%3D",
+ Invidious::Search::Filters::Features::FourK => "EgJwAQ%3D%3D",
+ Invidious::Search::Filters::Features::HD => "EgIgAQ%3D%3D",
+ Invidious::Search::Filters::Features::Subtitles => "EgIoAQ%3D%3D",
+ Invidious::Search::Filters::Features::CCommons => "EgIwAQ%3D%3D",
+ Invidious::Search::Filters::Features::ThreeSixty => "EgJ4AQ%3D%3D",
+ Invidious::Search::Filters::Features::VR180 => "EgPQAQE%3D",
+ Invidious::Search::Filters::Features::ThreeD => "EgI4AQ%3D%3D",
+ Invidious::Search::Filters::Features::HDR => "EgPIAQE%3D",
+ Invidious::Search::Filters::Features::Location => "EgO4AQE%3D",
+ Invidious::Search::Filters::Features::Purchased => "EgJIAQ%3D%3D",
+}
+
+SORT_FILTERS = {
+ Invidious::Search::Filters::Sort::Relevance => "",
+ Invidious::Search::Filters::Sort::Date => "CAI%3D",
+ Invidious::Search::Filters::Sort::Views => "CAM%3D",
+ Invidious::Search::Filters::Sort::Rating => "CAE%3D",
+}
+
+Spectator.describe Invidious::Search::Filters do
+ # -------------------
+ # Encode YT params
+ # -------------------
+
+ describe "#to_yt_params" do
+ sample DATE_FILTERS do |value, result|
+ it "Encodes upload date filter '#{value}'" do
+ expect(described_class.new(date: value).to_yt_params).to eq(result)
+ end
+ end
+
+ sample TYPE_FILTERS do |value, result|
+ it "Encodes content type filter '#{value}'" do
+ expect(described_class.new(type: value).to_yt_params).to eq(result)
+ end
+ end
+
+ sample DURATION_FILTERS do |value, result|
+ it "Encodes duration filter '#{value}'" do
+ expect(described_class.new(duration: value).to_yt_params).to eq(result)
+ end
+ end
+
+ sample FEATURE_FILTERS do |value, result|
+ it "Encodes feature filter '#{value}'" do
+ expect(described_class.new(features: value).to_yt_params).to eq(result)
+ end
+ end
+
+ sample SORT_FILTERS do |value, result|
+ it "Encodes sort filter '#{value}'" do
+ expect(described_class.new(sort: value).to_yt_params).to eq(result)
+ end
+ end
+ end
+
+ # -------------------
+ # Decode YT params
+ # -------------------
+
+ describe "#from_yt_params" do
+ sample DATE_FILTERS do |value, encoded|
+ it "Decodes upload date filter '#{value}'" do
+ params = HTTP::Params.parse("sp=#{encoded}")
+
+ expect(described_class.from_yt_params(params))
+ .to eq(described_class.new(date: value))
+ end
+ end
+
+ sample TYPE_FILTERS do |value, encoded|
+ it "Decodes content type filter '#{value}'" do
+ params = HTTP::Params.parse("sp=#{encoded}")
+
+ expect(described_class.from_yt_params(params))
+ .to eq(described_class.new(type: value))
+ end
+ end
+
+ sample DURATION_FILTERS do |value, encoded|
+ it "Decodes duration filter '#{value}'" do
+ params = HTTP::Params.parse("sp=#{encoded}")
+
+ expect(described_class.from_yt_params(params))
+ .to eq(described_class.new(duration: value))
+ end
+ end
+
+ sample FEATURE_FILTERS do |value, encoded|
+ it "Decodes feature filter '#{value}'" do
+ params = HTTP::Params.parse("sp=#{encoded}")
+
+ expect(described_class.from_yt_params(params))
+ .to eq(described_class.new(features: value))
+ end
+ end
+
+ sample SORT_FILTERS do |value, encoded|
+ it "Decodes sort filter '#{value}'" do
+ params = HTTP::Params.parse("sp=#{encoded}")
+
+ expect(described_class.from_yt_params(params))
+ .to eq(described_class.new(sort: value))
+ end
+ end
+ end
+end
diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr
index 09320750..6c492e2f 100644
--- a/spec/spec_helper.cr
+++ b/spec/spec_helper.cr
@@ -8,7 +8,7 @@ require "../src/invidious/channels/*"
require "../src/invidious/videos"
require "../src/invidious/comments"
require "../src/invidious/playlists"
-require "../src/invidious/search"
+require "../src/invidious/search/ctoken"
require "../src/invidious/trending"
require "spectator"