import requests from flask import Blueprint, render_template, request, flash, g, url_for, redirect from werkzeug.exceptions import NotFound from ..common.common import * from ..common.innertube import * from .lib import * from .protobuf import make_sp, make_channel_params, make_playlist_params frontend = Blueprint('browse', __name__, template_folder='templates', static_folder='static', static_url_path='/static/ys') @frontend.route('/results') @frontend.route('/search') def search(): #token = getattr(current_user, 'token', 'guest') q = request.args.get('q') or request.args.get('search_query') page = int(request.args.get('page') or 1) sp = make_sp(**{ k:v for k,v in request.args.items() if k in ['sort','date','type','len'] }, extras=['dont_fix_spelling']*0) # extras disabled if q: yt_results = fetch_searchresults(q, page, sp) results, extras = prepare_searchresults(yt_results) for extra in extras: flash(extra, 'info') else: results = None return render_template('search.html.j2', rows=results, query=q, page=page) # TODO: channels, playlists: # https://github.com/iv-org/invidious/blob/452d1e8307d6344dd51c5437ccd032a566291c34/src/invidious/channels.cr#L399 @frontend.route('/channel//') @frontend.route('/channel//') def channel(channel_id, subpage="videos"): if subpage == "videos": page = int(request.args.get('page', 1)) sort_by = request.args.get('sort') or "newest" query = None elif subpage == "playlists": page = None # TODO: cursor sort_by = request.args.get('sort', "modified") query = None elif subpage == "search": query = request.args.get('q') page = int(request.args.get('page', 1)) sort_by = None else: # we don't support /home, /about, ..., so redirect to /videos. return redirect(url_for('.channel', channel_id=channel_id)) # best effort; if it fails, it fails in the redirect. if not re.match(r"(UC[A-Za-z0-9_-]{22})", channel_id): return redirect(url_for('.channel_redirect', user=channel_id)) result = fetch_ajax(make_channel_params(channel_id, subpage, page, sort_by, query)) # Note: as of 2020-08-15, using the v1 format sometimes returns an error. if that's the case, try v3. alert = listget(listget(result,1,{}).get('response',{}).get('alerts',[]),0,{}).get('alertRenderer',{}) if alert.get('type','') == "ERROR": # alert['text']['simpleText'] == "Unknown error." result = fetch_ajax(make_channel_params(channel_id, subpage, page, sort_by, query, v3=True)) title, descr, thumb, rows, more = prepare_channel(result, channel_id) # TODO: add is_pinned/is_hidden if title is None: # if both v1 and v3 failed, fall back to xmlfeed: flash("unable to fetch results from ajax; displaying fallback results (15 newest)", "error") return fallback_route(channel_id, subpage) return render_template('channel.html.j2', title=title, subpage=subpage, rows=rows, channel_id=channel_id, channel_img=thumb, channel_desc=descr, page=page, has_more=more) @frontend.route('/user//') @frontend.route('/user//') @frontend.route('/c//') @frontend.route('/c//') def channel_redirect(user, subpage=None): """ The browse_ajax 'API' needs the UCID. We can get that from the RSS feeds. """ # inverse of the test in /channel/: if re.match(r"(UC[A-Za-z0-9_-]{22})", user): return redirect(url_for('.channel', channel_id=user)) xmlfeed = fetch_xml("user", user) if not xmlfeed: raise NotFound("channel appears to not exist") _, _, _, channel_id, _ = parse_xml(xmlfeed) return redirect( url_for('.channel', channel_id=channel_id, subpage=subpage), 308 ) @frontend.route('/playlist') def playlist(): #TODO: if anything goes wrong, fall back to xmlfeed playlist_id = request.args.get('list') if not playlist_id: return "bad list id", 400 # todo page = int(request.args.get('page', 1)) xmlfeed = fetch_xml("playlist_id", playlist_id) if not xmlfeed: return "not found or something", 404 # XXX title, author, _, channel_id, _ = parse_xml(xmlfeed) offset = (page-1)*100 # each call returns 100 items result = fetch_ajax(make_playlist_params(playlist_id, offset)) if not 'continuationContents' in result[1]['response']: # XXX: this needs cleanup! # code:"CONDITION_NOT_MET", debugInfo:"list type not viewable" # on playlist https://www.youtube.com/watch?v=6y_NJg-xoeE&list=RDgohHV9ryp-A&index=24 (not openable on yt.com) error = result[1]['response']['responseContext']['errors']['error'][0] flash(f"{error['code']}: {error['debugInfo'] or error['externalErrorMessage']}", 'error') return fallback_route() rows, more = prepare_playlist(result) return render_template('playlist.html.j2', title=title, author=author, channel_id=channel_id, rows=rows, page=page, has_more=more) @frontend.before_app_request def inject_button(): if not 'header_items' in g: g.header_items = [] g.header_items.append({ 'name': 'search', 'url': url_for('browse.search'), 'parent': frontend.name, 'priority': 15, })