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