]>
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
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 settings
= getattr ( current_user
, 'get_settings' , lambda : {})()
22 q
= request
. args
. get ( 'q' ) or request
. args
. get ( 'search_query' )
23 continuation
= request
. args
. get ( 'continuation' )
26 k
: v
for k
, v
in request
. args
. items ()
27 if k
in [ 'sort' , 'date' , 'type' , 'len' ]
29 f
for f
in request
. args
. getlist ( 'feature' )
30 if f
not in [ 'verbatim' ]
32 e
for e
in request
. args
. getlist ( 'feature' )
37 yt_results
= fetch_ajax ( "search" , **(
38 { 'continuation' : continuation
} if continuation
else { 'query' : q
, 'params' : sp
}
41 results
, extras
, continuation
= prepare_searchresults ( yt_results
)
42 results
= apply_video_flags ( token
, results
, settings
)
49 return render_template ( 'search.html.j2' , rows
= results
, query
= q
, continuation
= continuation
)
51 @frontend . route ( '/channel/<channel_id>/' )
52 @frontend . route ( '/channel/<channel_id>/<subpage>' )
53 def channel ( channel_id
, subpage
= "videos" ):
54 token
= getattr ( current_user
, 'token' , 'guest' )
55 settings
= getattr ( current_user
, 'get_settings' , lambda : {})()
56 if subpage
in ( "videos" , "streams" , "shorts" ): # "streams"==livestreams
57 sort_by
= request
. args
. get ( 'sort' ) or "newest"
59 elif subpage
== "playlists" :
60 sort_by
= request
. args
. get ( 'sort' , "modified" )
62 elif subpage
== "search" :
63 query
= request
. args
. get ( 'q' )
65 else : # we don't support /home, /about, ..., so redirect to /videos.
66 return redirect ( url_for ( '.channel' , channel_id
= channel_id
))
68 # best effort; if it fails, it fails in the redirect.
69 if not re
. match ( r
"(UC[A-Za-z0-9_-] {22} )" , channel_id
):
70 return redirect ( url_for ( '.channel_redirect' , user
= channel_id
))
72 # if we don't have a continuation, we create parameters for page 1 manually:
73 continuation
= request
. args
. get ( 'continuation' ) or \
74 make_channel_params ( channel_id
, subpage
, sort_by
, query
)
75 result
= fetch_ajax ( "browse" , continuation
= continuation
)
76 error
= find_and_parse_error ( result
)
78 if result
is None and subpage
== "videos" : # if fetching from innertube failed, fall back to xmlfeed:
79 flash ( "unable to fetch results from ajax; displaying fallback results (15 newest)" , "error" )
80 return fallback_route ( channel_id
, subpage
)
83 # mostly 'This channel does not exist' or 'This account has been terminated', hence 404
86 # new seperated videos/livestreams/shorts don't return metadata
87 xmlfeed
= fetch_xml ( "channel_id" , channel_id
)
89 title
, _
, _
, _
, _
= parse_xml ( xmlfeed
)
91 _
, descr
, thumb
, rows
, continuation
= prepare_channel ( result
, channel_id
, title
)
92 if not rows
and subpage
== "videos" : # overran end of list, or is special channel (e.g. music topic (sidebar 'best of youtube', UC-9-kyTW8ZkZNDHQJ6FgpwQ)
93 flash ( "ajax returned nothing; displaying fallback results (15 newest)" , "error" )
94 return fallback_route ( channel_id
, subpage
)
96 # set pin/hide stati of retrieved videos:
97 rows
= apply_video_flags ( token
, rows
, settings
)
99 with sqlite3
. connect ( cf
[ 'global' ][ 'database' ]) as conn
:
104 WHERE channel_id = ? AND user = ?
105 """ , ( channel_id
, token
))
106 ( is_subscribed
,) = c
. fetchone ()
108 return render_template ( 'channel.html.j2' ,
113 channel_id
= channel_id
,
116 is_subscribed
= is_subscribed
,
117 continuation
= continuation
)
119 @frontend . route ( '/<user>/<subpage>' )
120 @frontend . route ( '/user/<user>/' )
121 @frontend . route ( '/user/<user>/<subpage>' )
122 @frontend . route ( '/c/<user>/' )
123 @frontend . route ( '/c/<user>/<subpage>' )
124 def channel_redirect ( user
, subpage
= None ):
126 The browse_ajax 'API' needs the UCID.
129 # inverse of the test in /channel/:
130 if re
. match ( r
"(UC[A-Za-z0-9_-] {22} )" , user
):
131 return redirect ( url_for ( '.channel' , channel_id
= user
))
133 if subpage
not in ( None , "home" , "videos" , "shorts" , "streams" , "playlists" , "community" , "channels" , "about" ):
134 raise NotFound ( "not a valid channel subpage" )
136 channel_id
= canonicalize_channel ( request
. path
)
138 raise NotFound ( "channel does not exist" )
141 url_for ( '.channel' , channel_id
= channel_id
, subpage
= subpage
), 308
144 @frontend . route ( '/playlist' )
146 token
= getattr ( current_user
, 'token' , 'guest' )
147 settings
= getattr ( current_user
, 'get_settings' , lambda : {})()
148 playlist_id
= request
. args
. get ( 'list' )
150 raise BadRequest ( "No playlist ID" )
152 # if we don't have a continuation, we create parameters for page 1 manually:
153 continuation
= request
. args
. get ( 'continuation' ) or \
154 make_playlist_params ( playlist_id
, 0 )
155 result
= fetch_ajax ( "browse" , continuation
= continuation
)
156 error
= find_and_parse_error ( result
)
159 flash ( f
"1 {error} . Loading fallback." , 'error' )
160 return fallback_route ()
162 if not 'continuationContents' in result
:
163 flash ( f
"2 {error} . Loading fallback." , 'error' )
164 return fallback_route ()
166 title
, author
, channel_id
, rows
, continuation
= prepare_playlist ( result
)
167 rows
= apply_video_flags ( token
, rows
, settings
)
169 return render_template ( 'playlist.html.j2' ,
172 channel_id
= channel_id
,
174 continuation
= continuation
)
176 @frontend . route ( '/<something>' , strict_slashes
= False )
177 def plain_user_or_video ( something
):
178 # this is a near-copy of the same route in app/youtube, but using a
179 # different, more reliable endpoint to determine whether a channel exists.
181 # prevent a lot of false-positives (and reduce youtube api calls)
184 channel_id
= canonicalize_channel ( something
) # /vanity or /@handle
186 return redirect ( url_for ( '.channel' , channel_id
= channel_id
))
187 elif re
. match ( r
"^[-_0-9A-Za-z] {11} $" , something
): # looks like a video id
188 return redirect ( url_for ( 'youtube.watch' , v
= something
, t
= request
. args
. get ( 't' )))
190 raise NotFound ( "Note: some usernames not recognized; try searching it" )
192 @frontend . before_app_request
194 if not 'header_items' in g
:
196 g
. header_items
. append ({
198 'url' : url_for ( 'browse.search' ),
199 'parent' : frontend
. name
,