mirror of
				https://github.com/iv-org/invidious.git
				synced 2025-10-31 04:32:02 +00:00 
			
		
		
		
	Add a function to parse invidious legacy search filters
This commit is contained in:
		
							
								
								
									
										178
									
								
								spec/invidious/search/iv_filters_spec.cr
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								spec/invidious/search/iv_filters_spec.cr
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,178 @@ | |||||||
|  | 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 | ||||||
|  | end | ||||||
| @@ -77,6 +77,121 @@ module Invidious::Search | |||||||
|       @features : Features = Features::None, |       @features : Features = Features::None, | ||||||
|       @sort : Sort = Sort::Relevance |       @sort : Sort = Sort::Relevance | ||||||
|     ) |     ) | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     # ------------------- | ||||||
|  |     #  Invidious params | ||||||
|  |     # ------------------- | ||||||
|  |  | ||||||
|  |     def self.parse_features(raw : Array(String)) : Features | ||||||
|  |       # Initialize return variable | ||||||
|  |       features = Features.new(0) | ||||||
|  |  | ||||||
|  |       raw.each do |ft| | ||||||
|  |         case ft.downcase | ||||||
|  |         when "live", "livestream" | ||||||
|  |           features = features | Features::Live | ||||||
|  |         when "4k"        then features = features | Features::FourK | ||||||
|  |         when "hd"        then features = features | Features::HD | ||||||
|  |         when "subtitles" then features = features | Features::Subtitles | ||||||
|  |         when "creative_commons", "commons", "cc" | ||||||
|  |           features = features | Features::CCommons | ||||||
|  |         when "360"       then features = features | Features::ThreeSixty | ||||||
|  |         when "vr180"     then features = features | Features::VR180 | ||||||
|  |         when "3d"        then features = features | Features::ThreeD | ||||||
|  |         when "hdr"       then features = features | Features::HDR | ||||||
|  |         when "location"  then features = features | Features::Location | ||||||
|  |         when "purchased" then features = features | Features::Purchased | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  |  | ||||||
|  |       return features | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     def self.format_features(features : Features) : String | ||||||
|  |       # Directly return an empty string if there are no features | ||||||
|  |       return "" if features.none? | ||||||
|  |  | ||||||
|  |       # Initialize return variable | ||||||
|  |       str = [] of String | ||||||
|  |  | ||||||
|  |       str << "live" if features.live? | ||||||
|  |       str << "4k" if features.four_k? | ||||||
|  |       str << "hd" if features.hd? | ||||||
|  |       str << "subtitles" if features.subtitles? | ||||||
|  |       str << "commons" if features.c_commons? | ||||||
|  |       str << "360" if features.three_sixty? | ||||||
|  |       str << "vr180" if features.vr180? | ||||||
|  |       str << "3d" if features.three_d? | ||||||
|  |       str << "hdr" if features.hdr? | ||||||
|  |       str << "location" if features.location? | ||||||
|  |       str << "purchased" if features.purchased? | ||||||
|  |  | ||||||
|  |       return str.join(',') | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     def self.from_legacy_filters(str : String) : {Filters, String, String, Bool} | ||||||
|  |       # Split search query on spaces | ||||||
|  |       members = str.split(' ') | ||||||
|  |  | ||||||
|  |       # Output variables | ||||||
|  |       channel = "" | ||||||
|  |       filters = Filters.new | ||||||
|  |       subscriptions = false | ||||||
|  |  | ||||||
|  |       # Array to hold the non-filter members | ||||||
|  |       query = [] of String | ||||||
|  |  | ||||||
|  |       # Parse! | ||||||
|  |       members.each do |substr| | ||||||
|  |         # Separator operators | ||||||
|  |         operators = substr.split(':') | ||||||
|  |  | ||||||
|  |         case operators[0] | ||||||
|  |         when "user", "channel" | ||||||
|  |           next if operators.size != 2 | ||||||
|  |           channel = operators[1] | ||||||
|  |           # | ||||||
|  |         when "type", "content_type" | ||||||
|  |           next if operators.size != 2 | ||||||
|  |           type = Type.parse?(operators[1]) | ||||||
|  |           filters.type = type if !type.nil? | ||||||
|  |           # | ||||||
|  |         when "date" | ||||||
|  |           next if operators.size != 2 | ||||||
|  |           date = Date.parse?(operators[1]) | ||||||
|  |           filters.date = date if !date.nil? | ||||||
|  |           # | ||||||
|  |         when "duration" | ||||||
|  |           next if operators.size != 2 | ||||||
|  |           duration = Duration.parse?(operators[1]) | ||||||
|  |           filters.duration = duration if !duration.nil? | ||||||
|  |           # | ||||||
|  |         when "feature", "features" | ||||||
|  |           next if operators.size != 2 | ||||||
|  |           features = parse_features(operators[1].split(',')) | ||||||
|  |           filters.features = features if !features.nil? | ||||||
|  |           # | ||||||
|  |         when "sort" | ||||||
|  |           next if operators.size != 2 | ||||||
|  |           sort = Sort.parse?(operators[1]) | ||||||
|  |           filters.sort = sort if !sort.nil? | ||||||
|  |           # | ||||||
|  |         when "subscriptions" | ||||||
|  |           next if operators.size != 2 | ||||||
|  |           subscriptions = {"true", "on", "yes", "1"}.any?(&.== operators[1]) | ||||||
|  |           # | ||||||
|  |         else | ||||||
|  |           query << substr | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  |  | ||||||
|  |       # Re-assemble query (without filters) | ||||||
|  |       cleaned_query = query.join(' ') | ||||||
|  |  | ||||||
|  |       return {filters, channel, cleaned_query, subscriptions} | ||||||
|  |     end | ||||||
|  |  | ||||||
|     # ------------------- |     # ------------------- | ||||||
|     #  Youtube params |     #  Youtube params | ||||||
|     # ------------------- |     # ------------------- | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Samantaz Fox
					Samantaz Fox