From eaa6b44364220a123a65cd13025988257df938ad Mon Sep 17 00:00:00 2001 From: girst Date: Wed, 5 Aug 2020 00:58:59 +0200 Subject: [PATCH] implement search params sadly, pure-protobuf is a small 3rd-party dependency :( --- app/common/innertube.py | 12 +++-- app/dangerous/__init__.py | 7 ++- app/dangerous/protobuf.py | 44 ++++++++++++++++ app/dangerous/templates/search.html.j2 | 71 ++++++++++++++++++++++---- config/requirements.txt | 1 + 5 files changed, 120 insertions(+), 15 deletions(-) create mode 100644 app/dangerous/protobuf.py diff --git a/app/common/innertube.py b/app/common/innertube.py index 3a958ce..b596109 100644 --- a/app/common/innertube.py +++ b/app/common/innertube.py @@ -2,13 +2,16 @@ from urllib.parse import parse_qs, urlparse -def listfind(obj, key): +def findall(obj, key): """ given a list of dicts, where one dict contains a given key, return said key. """ - return next(iter([ obj[key] for obj in obj if key in obj.keys() ]),{}) + return [ obj[key] for obj in obj if key in obj.keys() ] def listget(obj, index, fallback=None): return next(iter(obj[index:]), fallback) +flatten = lambda l: [item for sublist in l for item in sublist] # https://stackoverflow.com/a/952952 +first = lambda l: next(iter(l),{}) +listfind = lambda obj,key: first(findall(obj,key)) def prepare_searchresults(yt_results): contents = listfind(yt_results, 'response') \ @@ -17,9 +20,10 @@ def prepare_searchresults(yt_results): .get('primaryContents',{})\ .get('sectionListRenderer',{})\ .get('contents',[]) - contents = listfind(contents, 'itemSectionRenderer').get('contents',[]) + contents = flatten([c.get('contents',[]) for c in findall(contents, 'itemSectionRenderer')]) return parse_result_items(contents) + def prepare_infocards(metadata): cards = metadata.get('cards',{}).get('cardCollectionRenderer',{}).get('cards',[]) return [parse_infocard(card) for card in cards if card is not None] @@ -132,6 +136,8 @@ def parse_result_items(items): ]) elif key == 'movieRenderer': # movies to buy/rent pass + elif key == 'carouselAdRenderer': # haha, no. + pass elif key == 'horizontalCardListRenderer': # suggested searches: .cards[].searchRefinementCardRenderer.query.runs[].text pass diff --git a/app/dangerous/__init__.py b/app/dangerous/__init__.py index af5b81e..814a3f3 100644 --- a/app/dangerous/__init__.py +++ b/app/dangerous/__init__.py @@ -7,6 +7,7 @@ from flask import Blueprint, render_template, request, flash, g, url_for from ..common.common import * from ..common.innertube import * from .lib import * +from .protobuf import make_sp frontend = Blueprint('dangerous', __name__, template_folder='templates', @@ -19,8 +20,10 @@ def search(): q = request.args.get('q') page = int(request.args.get('page', 1)) - sp = None #TODO: search filters (protobuf) - # https://github.com/iv-org/invidious/blob/452d1e8307d6344dd51c5437ccd032a566291c34/src/invidious/search.cr#L266 + sp = make_sp(**{ + k:v for k,v in request.args.items() + if k in ['sort','date','type','len'] + }) if q: yt_results = fetch_searchresults(q, page, sp) diff --git a/app/dangerous/protobuf.py b/app/dangerous/protobuf.py new file mode 100644 index 0000000..10496b3 --- /dev/null +++ b/app/dangerous/protobuf.py @@ -0,0 +1,44 @@ +import base64 +from dataclasses import dataclass +from typing import Optional + +from pure_protobuf.dataclasses_ import field, message +from pure_protobuf.types import int64 + +@message +@dataclass +class Filters: # adapted from invidious + date: Optional[int64] = field(1, default=None) + type: Optional[int64] = field(2, default=None) + length: Optional[int64] = field(3, default=None) + is_hd: Optional[bool] = field(4, default=None) + subtitles: Optional[bool] = field(5, default=None) + ccommons: Optional[bool] = field(6, default=None) + is_3d: Optional[bool] = field(7, default=None) + live: Optional[bool] = field(8, default=None) + purchased: Optional[bool] = field(9, default=None) + is_4k: Optional[bool] = field(14, default=None) + is_360: Optional[bool] = field(15, default=None) + location: Optional[bool] = field(23, default=None) + is_hdr: Optional[bool] = field(25, default=None) +@message +@dataclass +class SearchRequest: + sorted: Optional[int64] = field(1, default=None) + filter: Optional[Filters] = field(2, default=None) + +def make_sp(sort=None, date=None, type=None, len=None, features=[]): + sortorder = dict(relevance=0, rating=1, date=2, views=3) + datefilter = dict(hour=1, day=2, week=3, month=4, year=5) + typefilter = dict(video=1, channel=2, playlist=3, movie=4, show=5) + lenfilter = dict(short=1, long=2) + + return base64.b64encode(SearchRequest( + sorted=sortorder.get(sort), + filter=Filters( + date=datefilter.get(date), + type=typefilter.get(type), + length=lenfilter.get(len), + **{f:True for f in features}, + ) if date or type or len or features else None + ).dumps()).decode('ascii') diff --git a/app/dangerous/templates/search.html.j2 b/app/dangerous/templates/search.html.j2 index 985be56..1ae40ad 100644 --- a/app/dangerous/templates/search.html.j2 +++ b/app/dangerous/templates/search.html.j2 @@ -8,26 +8,77 @@ {% block content %} -
- - + +
+ + +
+
Filters
+ + + + +
{% if rows is not none %} diff --git a/config/requirements.txt b/config/requirements.txt index e4dee9a..5ae426a 100644 --- a/config/requirements.txt +++ b/config/requirements.txt @@ -4,3 +4,4 @@ gunicorn[eventlet] # Alternatively, install gunicorn and use 'gthread' worker requests requests-cache python-dateutil +pure-protobuf -- 2.39.3