]>
git.gir.st - subscriptionfeed.git/blob - app/browse/__init__.py
3 from flask
import Blueprint
, render_template
, request
, flash
, g
, url_for
, redirect
4 from flask_login
import current_user
5 from werkzeug
.exceptions
import BadRequest
, NotFound
7 from ..common
.common
import *
9 from .innertube
import prepare_searchresults
, prepare_channel
, prepare_playlist
10 from .protobuf
import make_sp
, make_channel_params
, make_playlist_params
, Filters
12 frontend
= Blueprint('browse', __name__
,
13 template_folder
='templates',
14 static_folder
='static',
15 static_url_path
='/static/ys')
17 @frontend.route('/results')
18 @frontend.route('/search')
20 token
= getattr(current_user
, 'token', 'guest')
21 q
= request
.args
.get('q') or request
.args
.get('search_query')
22 continuation
= request
.args
.get('continuation')
25 k
:v
for k
,v
in request
.args
.items()
26 if k
in ['sort','date','type','len']
28 f
for f
in request
.args
.getlist('feature')
29 if f
in Filters
.__dataclass
_fields
__.keys()
31 e
for e
in request
.args
.getlist('feature')
36 yt_results
= fetch_ajax("search", **(
37 {'continuation': continuation
} if continuation
else {'query': q
, 'params': sp
}
40 results
, extras
, continuation
= prepare_searchresults(yt_results
)
41 results
= apply_video_flags(token
, results
)
48 return render_template('search.html.j2', rows
=results
, query
=q
, continuation
=continuation
)
50 @frontend.route('/channel/<channel_id>/')
51 @frontend.route('/channel/<channel_id>/<subpage>')
52 def channel(channel_id
, subpage
="videos"):
53 token
= getattr(current_user
, 'token', 'guest')
54 if subpage
in ("videos", "streams", "shorts"): # "streams"==livestreams
55 sort_by
= request
.args
.get('sort') or "newest"
57 elif subpage
== "playlists":
58 sort_by
= request
.args
.get('sort', "modified")
60 elif subpage
== "search":
61 query
= request
.args
.get('q')
63 else: # we don't support /home, /about, ..., so redirect to /videos.
64 return redirect(url_for('.channel', channel_id
=channel_id
))
66 # best effort; if it fails, it fails in the redirect.
67 if not re
.match(r
"(UC[A-Za-z0-9_-]{22})", channel_id
):
68 return redirect(url_for('.channel_redirect', user
=channel_id
))
70 # if we don't have a continuation, we create parameters for page 1 manually:
71 continuation
= request
.args
.get('continuation') or \
72 make_channel_params(channel_id
, subpage
, 1, sort_by
, query
, v3
=(subpage
!= "search"))
73 result
= fetch_ajax("browse", continuation
=continuation
)
74 error
= find_and_parse_error(result
)
76 if result
is None: # if fetching from innertube failed, fall back to xmlfeed:
77 flash("unable to fetch results from ajax; displaying fallback results (15 newest)", "error")
78 return fallback_route(channel_id
, subpage
)
81 return error
, 400 # todo: ugly
83 # new seperated videos/livestreams/shorts don't return metadata
84 xmlfeed
= fetch_xml("channel_id", channel_id
)
86 title
, _
, _
, _
, _
= parse_xml(xmlfeed
)
88 _
, descr
, thumb
, rows
, continuation
= prepare_channel(result
, channel_id
, title
)
89 if not rows
: # overran end of list, or is special channel (e.g. music topic (sidebar 'best of youtube', UC-9-kyTW8ZkZNDHQJ6FgpwQ)
90 flash("ajax returned nothing; displaying fallback results (15 newest)", "error")
91 return fallback_route(channel_id
, subpage
)
93 # set pin/hide stati of retrieved videos:
94 rows
= apply_video_flags(token
, rows
)
96 with sqlite3
.connect(cf
['global']['database']) as conn
:
101 WHERE channel_id = ? AND user = ?
102 """, (channel_id
, token
))
103 (is_subscribed
,) = c
.fetchone()
105 return render_template('channel.html.j2',
110 channel_id
=channel_id
,
113 is_subscribed
=is_subscribed
,
114 continuation
=continuation
)
116 @frontend.route('/<user>/<subpage>')
117 @frontend.route('/user/<user>/')
118 @frontend.route('/user/<user>/<subpage>')
119 @frontend.route('/c/<user>/')
120 @frontend.route('/c/<user>/<subpage>')
121 def channel_redirect(user
, subpage
=None):
123 The browse_ajax 'API' needs the UCID.
126 # inverse of the test in /channel/:
127 if re
.match(r
"(UC[A-Za-z0-9_-]{22})", user
):
128 return redirect(url_for('.channel', channel_id
=user
))
130 if subpage
not in (None, "home", "videos", "shorts", "streams", "playlists", "community", "channels", "about"):
131 raise NotFound("not a valid channel subpage")
133 channel_id
= canonicalize_channel(request
.path
)
135 raise NotFound("channel does not exist")
138 url_for('.channel', channel_id
=channel_id
, subpage
=subpage
), 308
141 @frontend.route('/playlist')
143 token
= getattr(current_user
, 'token', 'guest')
144 playlist_id
= request
.args
.get('list')
146 raise BadRequest("No playlist ID")
148 # if we don't have a continuation, we create parameters for page 1 manually:
149 continuation
= request
.args
.get('continuation') or \
150 make_playlist_params(playlist_id
, 0)
151 result
= fetch_ajax("browse", continuation
=continuation
)
152 error
= find_and_parse_error(result
)
155 flash(f
"1 {error}. Loading fallback.", 'error')
156 return fallback_route()
158 if not 'continuationContents' in result
:
159 flash(f
"2 {error}. Loading fallback.", 'error')
160 return fallback_route()
162 title
, author
, channel_id
, rows
, continuation
= prepare_playlist(result
)
163 rows
= apply_video_flags(token
, rows
)
165 return render_template('playlist.html.j2',
168 channel_id
=channel_id
,
170 continuation
=continuation
)
172 @frontend.route('/<something>', strict_slashes
=False)
173 def plain_user_or_video(something
):
174 # this is a near-copy of the same route in app/youtube, but using a
175 # different, more reliable endpoint to determine whether a channel exists.
177 # prevent a lot of false-positives (and reduce youtube api calls)
180 channel_id
= canonicalize_channel(something
) # /vanity or /@handle
182 return redirect(url_for('.channel', channel_id
=channel_id
))
183 elif re
.match(r
"^[-_0-9A-Za-z]{11}$", something
): # looks like a video id
184 return redirect(url_for('youtube.watch', v
=something
, t
=request
.args
.get('t')))
186 raise NotFound("Note: some usernames not recognized; try searching it")
188 @frontend.before_app_request
190 if not 'header_items' in g
:
192 g
.header_items
.append({
194 'url': url_for('browse.search'),
195 'parent': frontend
.name
,