summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSamantaz Fox <coding@samantaz.fr>2022-03-26 22:18:42 +0100
committerSamantaz Fox <coding@samantaz.fr>2022-04-03 20:03:34 +0200
commita813955ad39c254240dcb02344d94d03d0bbd6b2 (patch)
tree22af5bef605158851e23c22a3bcbe44ba9c782a6 /src
parent1e3425fdee91f1b25f67d1e03872b68e978bc6e0 (diff)
downloadinvidious-a813955ad39c254240dcb02344d94d03d0bbd6b2.tar.gz
invidious-a813955ad39c254240dcb02344d94d03d0bbd6b2.tar.bz2
invidious-a813955ad39c254240dcb02344d94d03d0bbd6b2.zip
Add Search::Query class to handle search queries
Diffstat (limited to 'src')
-rw-r--r--src/invidious/search/filters.cr5
-rw-r--r--src/invidious/search/query.cr149
2 files changed, 154 insertions, 0 deletions
diff --git a/src/invidious/search/filters.cr b/src/invidious/search/filters.cr
index 8f4ada6c..0e8438b9 100644
--- a/src/invidious/search/filters.cr
+++ b/src/invidious/search/filters.cr
@@ -79,6 +79,11 @@ module Invidious::Search
)
end
+ def is_default? : Bool
+ return @date.none? && @type.all? && @duration.none? && \
+ @features.none? && @sort.relevance?
+ end
+
# -------------------
# Invidious params
# -------------------
diff --git a/src/invidious/search/query.cr b/src/invidious/search/query.cr
new file mode 100644
index 00000000..4d76b083
--- /dev/null
+++ b/src/invidious/search/query.cr
@@ -0,0 +1,149 @@
+module Invidious::Search
+ class Query
+ enum Type
+ # Types related to YouTube
+ Regular # Youtube search page
+ Channel # Youtube channel search box
+
+ # Types specific to Invidious
+ Subscriptions # Search user subscriptions
+ Playlist # "Add playlist item" search
+ end
+
+ @type : Type = Type::Regular
+
+ @raw_query : String
+ @query : String = ""
+
+ property filters : Filters = Filters.new
+ property page : Int32
+ property region : String?
+ property channel : String = ""
+
+ # Return true if @raw_query is either `nil` or empty
+ private def empty_raw_query?
+ return @raw_query.empty?
+ end
+
+ # Same as `empty_raw_query?`, but named for external use
+ def empty?
+ return self.empty_raw_query?
+ end
+
+ # Getter for the query string.
+ # It is named `text` to reduce confusion (`search_query.text` makes more
+ # sense than `search_query.query`)
+ def text
+ return @query
+ end
+
+ # Initialize a new search query.
+ # Parameters are used to get the query string, the page number
+ # and the search filters (if any). Type tells this function
+ # where it is being called from (See `Type` above).
+ def initialize(
+ params : HTTP::Params,
+ @type : Type = Type::Regular,
+ @region : String? = nil
+ )
+ # Get the raw search query string (common to all search types). In
+ # Regular search mode, also look for the `search_query` URL parameter
+ if @type.regular?
+ @raw_query = params["q"]? || params["search_query"]? || ""
+ else
+ @raw_query = params["q"]? || ""
+ end
+
+ # Get the page number (also common to all search types)
+ @page = params["page"]?.try &.to_i? || 1
+
+ # Stop here is raw query in empty
+ # NOTE: maybe raise in the future?
+ return if self.empty_raw_query?
+
+ # Specific handling
+ case @type
+ when .playlist?, .channel?
+ # In "add playlist item" mode, filters are parsed from the query
+ # string itself (legacy), and the channel is ignored.
+ #
+ # In "channel search" mode, filters are ignored, but we still parse
+ # the query prevent transmission of legacy filters to youtube.
+ #
+ @filters, @query, @channel, _ = Filters.from_legacy_filters(@raw_query || "")
+ #
+ when .subscriptions?, .regular?
+ if params["sp"]?
+ # Parse the `sp` URL parameter (youtube compatibility)
+ @filters = Filters.from_yt_params(params)
+ @query = @raw_query || ""
+ else
+ # Parse invidious URL parameters (sort, date, etc...)
+ @filters = Filters.from_iv_params(params)
+ @channel = params["channel"]? || ""
+
+ if @filters.default? && @raw_query.includes?(':')
+ # Parse legacy filters from query
+ @filters, @query, @channel, subs = Filters.from_legacy_filters(@raw_query || "")
+ else
+ @query = @raw_query || ""
+ end
+
+ if !@channel.empty?
+ # Switch to channel search mode (filters will be ignored)
+ @type = Type::Channel
+ elsif subs
+ # Switch to subscriptions search mode
+ @type = Type::Subscriptions
+ end
+ end
+ end
+ end
+
+ # Run the search query using the corresponding search processor.
+ # Returns either the results or an empty array of `SearchItem`.
+ def process(user : Invidious::User? = nil) : Array(SearchItem) | Array(ChannelVideo)
+ items = [] of SearchItem
+
+ # Don't bother going further if search query is empty
+ return items if self.empty_raw_query?
+
+ case @type
+ when .regular?, .playlist?
+ all_items = search(@query, @filters, @page, @region)
+ items = unnest_items(all_items)
+ #
+ when .channel?
+ items = Processors.channel(@query, @page, @channel)
+ #
+ when .subscriptions?
+ if user
+ items = Processors.subscriptions(self, user.as(Invidious::User))
+ end
+ end
+
+ return items
+ end
+
+ # TODO: clean code
+ private def unnest_items(all_items) : Array(SearchItem)
+ items = [] of SearchItem
+
+ # Light processing to flatten search results out of Categories.
+ # They should ideally be supported in the future.
+ all_items.each do |i|
+ if i.is_a? Category
+ i.contents.each do |nest_i|
+ if !nest_i.is_a? Video
+ items << nest_i
+ end
+ end
+ else
+ items << i
+ end
+ end
+
+ return items
+ end
+ end
+end