]>
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
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
= int(request
.args
.get('page') or 1)
24 k
:v
for k
,v
in request
.args
.items()
25 if k
in ['sort','date','type','len']
26 }, extras
=['dont_fix_spelling']*0) # extras disabled
29 yt_results
= fetch_searchresults(q
, page
, sp
)
31 results
, extras
= prepare_searchresults(yt_results
)
38 return render_template('search.html.j2', rows
=results
, query
=q
, page
=page
)
40 # TODO: channels, playlists:
41 # https://github.com/iv-org/invidious/blob/452d1e8307d6344dd51c5437ccd032a566291c34/src/invidious/channels.cr#L399
43 @frontend.route('/channel/<channel_id>/')
44 @frontend.route('/channel/<channel_id>/<subpage>')
45 def channel(channel_id
, subpage
="videos"):
46 token
= getattr(current_user
, 'token', 'guest')
47 if subpage
== "videos":
48 page
= int(request
.args
.get('page', 1))
49 sort_by
= request
.args
.get('sort') or "newest"
51 elif subpage
== "playlists":
52 page
= None # TODO: cursor
53 sort_by
= request
.args
.get('sort', "modified")
55 elif subpage
== "search":
56 query
= request
.args
.get('q')
57 page
= int(request
.args
.get('page', 1))
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 result
= fetch_ajax(make_channel_params(channel_id
, subpage
, page
, sort_by
, query
))
68 # Note: as of 2020-08-15, using the v1 format sometimes returns an error. if that's the case, try v3.
69 alert
= listget(listget(result
,1,{}).get('response',{}).get('alerts',[]),0,{}).get('alertRenderer',{})
70 if alert
.get('type','') == "ERROR": # alert['text']['simpleText'] == "Unknown error."
71 result
= fetch_ajax(make_channel_params(channel_id
, subpage
, page
, sort_by
, query
, v3
=True))
73 title
, descr
, thumb
, rows
, more
= prepare_channel(result
, channel_id
)
74 # TODO: add is_pinned/is_hidden
76 if title
is None: # if both v1 and v3 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
)
80 # set pin/hide stati of retrieved videos:
81 video_ids
= [card
['content']['video_id'] for card
in rows
]
82 pinned
, hidden
= fetch_video_flags(token
, video_ids
)
84 {'type':v
['type'], 'content':{**v
['content'], 'pinned': v
['content']['video_id'] in pinned
}}
86 if v
['content']['video_id'] not in hidden
87 ], key
=lambda v
:v
['content']['pinned'], reverse
=True)
89 with sqlite3
.connect(cf
['global']['database']) as conn
:
94 WHERE channel_id = ? AND user = ?
95 """, (channel_id
, token
))
96 (is_subscribed
,) = c
.fetchone()
98 return render_template('channel.html.j2',
102 channel_id
=channel_id
,
105 is_subscribed
=is_subscribed
,
109 @frontend.route('/user/<user>/')
110 @frontend.route('/user/<user>/<subpage>')
111 @frontend.route('/c/<user>/')
112 @frontend.route('/c/<user>/<subpage>')
113 def channel_redirect(user
, subpage
=None):
115 The browse_ajax 'API' needs the UCID. We can get that from the RSS feeds.
118 # inverse of the test in /channel/:
119 if re
.match(r
"(UC[A-Za-z0-9_-]{22})", user
):
120 return redirect(url_for('.channel', channel_id
=user
))
122 xmlfeed
= fetch_xml("user", user
)
124 raise NotFound("channel appears to not exist")
125 _
, _
, _
, channel_id
, _
= parse_xml(xmlfeed
)
127 url_for('.channel', channel_id
=channel_id
, subpage
=subpage
), 308
130 @frontend.route('/playlist')
132 #TODO: if anything goes wrong, fall back to xmlfeed
133 playlist_id
= request
.args
.get('list')
135 raise BadRequest("No playlist ID")
136 page
= int(request
.args
.get('page', 1))
138 xmlfeed
= fetch_xml("playlist_id", playlist_id
)
140 raise NotFound("Unable to fetch playlist")
141 title
, author
, _
, channel_id
, _
= parse_xml(xmlfeed
)
143 offset
= (page
-1)*100 # each call returns 100 items
144 result
= fetch_ajax(make_playlist_params(playlist_id
, offset
))
146 if not 'continuationContents' in result
[1]['response']: # XXX: this needs cleanup!
147 # code:"CONDITION_NOT_MET", debugInfo:"list type not viewable"
148 # on playlist https://www.youtube.com/watch?v=6y_NJg-xoeE&list=RDgohHV9ryp-A&index=24 (not openable on yt.com)
149 error
= result
[1]['response']['responseContext']['errors']['error'][0]
150 flash(f
"{error['code']}: {error['debugInfo'] or error['externalErrorMessage']}", 'error')
151 return fallback_route()
152 rows
, more
= prepare_playlist(result
)
154 return render_template('playlist.html.j2',
157 channel_id
=channel_id
,
162 @frontend.before_app_request
164 if not 'header_items' in g
:
166 g
.header_items
.append({
168 'url': url_for('browse.search'),
169 'parent': frontend
.name
,