]> git.gir.st - subscriptionfeed.git/blob - app/browse/__init__.py
rename 'dangerous' blueprint, as its safe
[subscriptionfeed.git] / app / browse / __init__.py
1 import requests
2 from flask import Blueprint, render_template, request, flash, g, url_for, redirect
3 from werkzeug.exceptions import NotFound
4
5 from ..common.common import *
6 from ..common.innertube import *
7 from .lib import *
8 from .protobuf import make_sp, make_channel_params, make_playlist_params
9
10 frontend = Blueprint('browse', __name__,
11 template_folder='templates',
12 static_folder='static',
13 static_url_path='/static/ys')
14
15 @frontend.route('/results')
16 @frontend.route('/search')
17 def search():
18 #token = getattr(current_user, 'token', 'guest')
19 q = request.args.get('q') or request.args.get('search_query')
20 page = int(request.args.get('page') or 1)
21
22 sp = make_sp(**{
23 k:v for k,v in request.args.items()
24 if k in ['sort','date','type','len']
25 }, extras=['dont_fix_spelling']*0) # extras disabled
26
27 if q:
28 yt_results = fetch_searchresults(q, page, sp)
29
30 results, extras = prepare_searchresults(yt_results)
31
32 for extra in extras:
33 flash(extra, 'info')
34 else:
35 results = None
36
37 return render_template('search.html.j2', rows=results, query=q, page=page)
38
39 # TODO: channels, playlists:
40 # https://github.com/iv-org/invidious/blob/452d1e8307d6344dd51c5437ccd032a566291c34/src/invidious/channels.cr#L399
41
42 @frontend.route('/channel/<channel_id>/')
43 @frontend.route('/channel/<channel_id>/<subpage>')
44 def channel(channel_id, subpage="videos"):
45 if subpage == "videos":
46 page = int(request.args.get('page', 1))
47 sort_by = request.args.get('sort') or "newest"
48 query = None
49 elif subpage == "playlists":
50 page = None # TODO: cursor
51 sort_by = request.args.get('sort', "modified")
52 query = None
53 elif subpage == "search":
54 query = request.args.get('q')
55 page = int(request.args.get('page', 1))
56 sort_by = None
57 else: # we don't support /home, /about, ..., so redirect to /videos.
58 return redirect(url_for('.channel', channel_id=channel_id))
59
60 # best effort; if it fails, it fails in the redirect.
61 if not re.match(r"(UC[A-Za-z0-9_-]{22})", channel_id):
62 return redirect(url_for('.channel_redirect', user=channel_id))
63
64 result = fetch_ajax(make_channel_params(channel_id, subpage, page, sort_by, query))
65
66 # Note: as of 2020-08-15, using the v1 format sometimes returns an error. if that's the case, try v3.
67 alert = listget(listget(result,1,{}).get('response',{}).get('alerts',[]),0,{}).get('alertRenderer',{})
68 if alert.get('type','') == "ERROR": # alert['text']['simpleText'] == "Unknown error."
69 result = fetch_ajax(make_channel_params(channel_id, subpage, page, sort_by, query, v3=True))
70
71 title, descr, thumb, rows, more = prepare_channel(result, channel_id)
72 # TODO: add is_pinned/is_hidden
73
74 if title is None: # if both v1 and v3 failed, fall back to xmlfeed:
75 flash("unable to fetch results from ajax; displaying fallback results (15 newest)", "error")
76 return fallback_route(channel_id, subpage)
77
78 return render_template('channel.html.j2',
79 title=title,
80 subpage=subpage,
81 rows=rows,
82 channel_id=channel_id,
83 channel_img=thumb,
84 channel_desc=descr,
85 page=page,
86 has_more=more)
87
88 @frontend.route('/user/<user>')
89 @frontend.route('/user/<user>/<subpage>')
90 @frontend.route('/c/<user>')
91 @frontend.route('/c/<user>/<subpage>')
92 def channel_redirect(user, subpage=None):
93 """
94 The browse_ajax 'API' needs the UCID. We can get that from the RSS feeds.
95 """
96
97 # inverse of the test in /channel/:
98 if re.match(r"(UC[A-Za-z0-9_-]{22})", user):
99 return redirect(url_for('.channel', channel_id=user))
100
101 xmlfeed = fetch_xml("user", user)
102 if not xmlfeed:
103 raise NotFound("channel appears to not exist")
104 _, _, _, channel_id, _ = parse_xml(xmlfeed)
105 return redirect(
106 url_for('.channel', channel_id=channel_id, subpage=subpage), 308
107 )
108
109 @frontend.route('/playlist')
110 def playlist():
111 #TODO: if anything goes wrong, fall back to xmlfeed
112 playlist_id = request.args.get('list')
113 if not playlist_id:
114 return "bad list id", 400 # todo
115 page = int(request.args.get('page', 1))
116
117 xmlfeed = fetch_xml("playlist_id", playlist_id)
118 if not xmlfeed:
119 return "not found or something", 404 # XXX
120 title, author, _, channel_id, _ = parse_xml(xmlfeed)
121
122 offset = (page-1)*100 # each call returns 100 items
123 result = fetch_ajax(make_playlist_params(playlist_id, offset))
124
125 if not 'continuationContents' in result[1]['response']: # XXX: this needs cleanup!
126 # code:"CONDITION_NOT_MET", debugInfo:"list type not viewable"
127 # on playlist https://www.youtube.com/watch?v=6y_NJg-xoeE&list=RDgohHV9ryp-A&index=24 (not openable on yt.com)
128 error = result[1]['response']['responseContext']['errors']['error'][0]
129 flash(f"{error['code']}: {error['debugInfo'] or error['externalErrorMessage']}", 'error')
130 return fallback_route()
131 rows, more = prepare_playlist(result)
132
133 return render_template('playlist.html.j2',
134 title=title,
135 author=author,
136 channel_id=channel_id,
137 rows=rows,
138 page=page,
139 has_more=more)
140
141 @frontend.before_app_request
142 def inject_button():
143 if not 'header_items' in g:
144 g.header_items = []
145 g.header_items.append({
146 'name': 'search',
147 'url': url_for('browse.search'),
148 'parent': frontend.name,
149 'priority': 15,
150 })
Imprint / Impressum