]>
git.gir.st - subscriptionfeed.git/blob - app/browse/__init__.py
2 from flask
import Blueprint
, render_template
, request
, flash
, g
, url_for
, redirect
3 from flask_login
import current_user
4 from werkzeug
.exceptions
import BadRequest
, NotFound
6 from ..common
.common
import *
7 from ..common
.innertube
import *
9 from .protobuf
import make_sp
, make_channel_params
, make_playlist_params
, Filters
11 frontend
= Blueprint('browse', __name__
,
12 template_folder
='templates',
13 static_folder
='static',
14 static_url_path
='/static/ys')
16 @frontend.route('/results')
17 @frontend.route('/search')
19 #token = getattr(current_user, 'token', 'guest')
20 q
= request
.args
.get('q') or request
.args
.get('search_query')
21 page
= request
.args
.get('page', 1, type=int)
23 sp
= make_sp(page
, **{
24 k
:v
for k
,v
in request
.args
.items()
25 if k
in ['sort','date','type','len']
27 f
for f
in request
.args
.getlist('feature')
28 if f
in Filters
.__dataclass
_fields
__.keys()
30 e
for e
in request
.args
.getlist('feature')
35 yt_results
= fetch_searchresults(q
, sp
)
37 results
, extras
= prepare_searchresults(yt_results
)
44 return render_template('search.html.j2', rows
=results
, query
=q
, page
=page
)
46 @frontend.route('/channel/<channel_id>/')
47 @frontend.route('/channel/<channel_id>/<subpage>')
48 def channel(channel_id
, subpage
="videos"):
49 token
= getattr(current_user
, 'token', 'guest')
50 if subpage
== "videos":
51 sort_by
= request
.args
.get('sort') or "newest"
53 elif subpage
== "playlists":
54 sort_by
= request
.args
.get('sort', "modified")
56 elif subpage
== "search":
57 query
= request
.args
.get('q')
59 else: # we don't support /home, /about, ..., so redirect to /videos.
60 return redirect(url_for('.channel', channel_id
=channel_id
))
62 # best effort; if it fails, it fails in the redirect.
63 if not re
.match(r
"(UC[A-Za-z0-9_-]{22})", channel_id
):
64 return redirect(url_for('.channel_redirect', user
=channel_id
))
66 # if we don't have a continuation, we create parameters for page 1 manually:
67 continuation
= request
.args
.get('continuation') or \
68 make_channel_params(channel_id
, subpage
, 1, sort_by
, query
, v3
=(subpage
!= "search"))
69 result
= fetch_ajax("browse", continuation
=continuation
)
70 error
= find_and_parse_error(result
)
72 if result
is None: # if fetching from innertube failed, fall back to xmlfeed:
73 flash("unable to fetch results from ajax; displaying fallback results (15 newest)", "error")
74 return fallback_route(channel_id
, subpage
)
77 return error
, 400 # todo: ugly
79 title
, descr
, thumb
, rows
, more
= prepare_channel(result
, channel_id
)
80 if not rows
: # overran end of list, or is special channel (e.g. music topic)
81 flash("ajax returned nothing; displaying fallback results (15 newest)", "error")
82 return fallback_route(channel_id
, subpage
)
84 # set pin/hide stati of retrieved videos:
85 video_ids
= [card
['content']['video_id'] for card
in rows
]
86 pinned
, hidden
= fetch_video_flags(token
, video_ids
)
88 {'type':v
['type'], 'content':{**v
['content'], 'pinned': v
['content']['video_id'] in pinned
}}
90 if v
['content']['video_id'] not in hidden
91 ], key
=lambda v
:v
['content']['pinned'], reverse
=True)
93 with sqlite3
.connect(cf
['global']['database']) as conn
:
98 WHERE channel_id = ? AND user = ?
99 """, (channel_id
, token
))
100 (is_subscribed
,) = c
.fetchone()
102 return render_template('channel.html.j2',
106 channel_id
=channel_id
,
109 is_subscribed
=is_subscribed
,
112 @frontend.route('/user/<user>/')
113 @frontend.route('/user/<user>/<subpage>')
114 @frontend.route('/c/<user>/')
115 @frontend.route('/c/<user>/<subpage>')
116 def channel_redirect(user
, subpage
=None):
118 The browse_ajax 'API' needs the UCID.
121 # inverse of the test in /channel/:
122 if re
.match(r
"(UC[A-Za-z0-9_-]{22})", user
):
123 return redirect(url_for('.channel', channel_id
=user
))
125 channel_id
= canonicalize_channel(user
)
127 raise NotFound("channel appears to not exist")
129 url_for('.channel', channel_id
=channel_id
, subpage
=subpage
), 308
132 @frontend.route('/playlist')
134 playlist_id
= request
.args
.get('list')
136 raise BadRequest("No playlist ID")
138 xmlfeed
= fetch_xml("playlist_id", playlist_id
)
140 raise NotFound("Unable to fetch playlist")
141 title
, author
, _
, channel_id
, _
= parse_xml(xmlfeed
)
143 # if we don't have a continuation, we create parameters for page 1 manually:
144 continuation
= request
.args
.get('continuation') or \
145 make_playlist_params(playlist_id
, 0)
146 result
= fetch_ajax("browse", continuation
=continuation
)
147 error
= find_and_parse_error(result
)
150 flash(f
"1 {error}. Loading fallback.", 'error')
151 return fallback_route()
153 if not 'continuationContents' in result
:
154 flash(f
"2 {error}. Loading fallback.", 'error')
155 return fallback_route()
157 rows
, more
= prepare_playlist(result
)
159 return render_template('playlist.html.j2',
162 channel_id
=channel_id
,
166 @frontend.route('/<something>', strict_slashes
=False)
167 def plain_user_or_video(something
):
168 # this is a near-copy of the same route in app/youtube, but using a
169 # different, more reliable endpoint to determine whether a channel exists.
171 # prevent a lot of false-positives (and reduce youtube api calls)
174 channel_id
= canonicalize_channel(something
)
176 return redirect(url_for('.channel', channel_id
=channel_id
))
177 elif re
.match(r
"^[-_0-9A-Za-z]{11}$", something
): # looks like a video id
178 return redirect(url_for('youtube.watch', v
=something
, t
=request
.args
.get('t')))
180 raise NotFound("Note: some usernames not recognized; try searching it")
182 @frontend.before_app_request
184 if not 'header_items' in g
:
186 g
.header_items
.append({
188 'url': url_for('browse.search'),
189 'parent': frontend
.name
,