Add a function to parse invidious legacy search filters
This commit is contained in:
parent
75c9dbaf6b
commit
c888524523
2 changed files with 293 additions and 0 deletions
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
|
||||||
# -------------------
|
# -------------------
|
||||||
|
|
Loading…
Reference in a new issue