Convert subscriptions page to flask framework

This commit is contained in:
James Taylor 2019-08-10 00:09:03 -07:00
parent 2e75c6d960
commit 163814d35c
4 changed files with 161 additions and 178 deletions

View File

@ -6,7 +6,7 @@ from youtube import yt_app
from youtube import util from youtube import util
# these are just so the files get run - they import yt_app and add routes to it # these are just so the files get run - they import yt_app and add routes to it
from youtube import watch, search, playlist, channel, local_playlist, comments, post_comment from youtube import watch, search, playlist, channel, local_playlist, comments, post_comment, subscriptions
import settings import settings

View File

@ -1,21 +1,18 @@
from youtube import util, yt_data_extract, html_common, channel from youtube import util, yt_data_extract, channel
from youtube import yt_app
import settings import settings
from string import Template
import sqlite3 import sqlite3
import os import os
import time import time
import gevent import gevent
import html
import json import json
import traceback import traceback
import contextlib import contextlib
import defusedxml.ElementTree import defusedxml.ElementTree
with open('yt_subscriptions_template.html', 'r', encoding='utf-8') as f: import flask
subscriptions_template = Template(f.read()) from flask import request
with open('yt_subscription_manager_template.html', 'r', encoding='utf-8') as f:
subscription_manager_template = Template(f.read())
thumbnails_directory = os.path.join(settings.data_dir, "subscription_thumbnails") thumbnails_directory = os.path.join(settings.data_dir, "subscription_thumbnails")
@ -272,16 +269,15 @@ def _get_upstream_videos(channel_id):
videos = [] videos = []
json_channel_videos = channel.get_grid_items(channel.get_channel_tab(channel_id)[1]['response']) channel_videos = channel.extract_info(json.loads(channel.get_channel_tab(channel_id)), 'videos')['items']
for i, json_video in enumerate(json_channel_videos): for i, video_item in enumerate(channel_videos):
info = yt_data_extract.renderer_info(json_video['gridVideoRenderer']) if 'description' not in video_item:
if 'description' not in info: video_item['description'] = ''
info['description'] = ''
try: try:
info['time_published'] = youtube_timestamp_to_posix(info['published']) - i # subtract a few seconds off the videos so they will be in the right order video_item['time_published'] = youtube_timestamp_to_posix(video_item['published']) - i # subtract a few seconds off the videos so they will be in the right order
except KeyError: except KeyError:
print(info) print(video_item)
videos.append((channel_id, info['id'], info['title'], info['duration'], info['time_published'], info['description'])) videos.append((channel_id, video_item['id'], video_item['title'], video_item['duration'], video_item['time_published'], video_item['description']))
now = time.time() now = time.time()
download_thumbnails_if_necessary(video[1] for video in videos if (now - video[4]) < 30*24*3600) # Don't download thumbnails from videos older than a month download_thumbnails_if_necessary(video[1] for video in videos if (now - video[4]) < 30*24*3600) # Don't download thumbnails from videos older than a month
@ -380,7 +376,7 @@ def import_subscriptions(env, start_response):
sub_list_item_template = Template(''' """sub_list_item_template = Template('''
<li class="sub-list-item $mute_class"> <li class="sub-list-item $mute_class">
<input class="sub-list-checkbox" name="channel_ids" value="$channel_id" form="subscription-manager-form" type="checkbox"> <input class="sub-list-checkbox" name="channel_ids" value="$channel_id" form="subscription-manager-form" type="checkbox">
<a href="$channel_url" class="sub-list-item-name" title="$channel_name">$channel_name</a> <a href="$channel_url" class="sub-list-item-name" title="$channel_name">$channel_name</a>
@ -394,7 +390,7 @@ tag_group_template = Template('''
$sub_list $sub_list
</ol> </ol>
</li> </li>
''') ''')"""
def get_subscription_manager_page(env, start_response): def get_subscription_manager_page(env, start_response):
with open_database() as connection: with open_database() as connection:
with connection as cursor: with connection as cursor:
@ -473,8 +469,8 @@ def list_from_comma_separated_tags(string):
return [tag.strip() for tag in string.split(',') if tag.strip()] return [tag.strip() for tag in string.split(',') if tag.strip()]
unsubscribe_list_item_template = Template(''' """unsubscribe_list_item_template = Template('''
<li><a href="$channel_url" title="$channel_name">$channel_name</a></li>''') <li><a href="$channel_url" title="$channel_name">$channel_name</a></li>''')"""
def post_subscription_manager_page(env, start_response): def post_subscription_manager_page(env, start_response):
params = env['parameters'] params = env['parameters']
action = params['action'][0] action = params['action'][0]
@ -531,97 +527,69 @@ def post_subscription_manager_page(env, start_response):
return b'' return b''
@yt_app.route('/subscriptions', methods=['GET'])
sidebar_tag_item_template = Template(''' @yt_app.route('/feed/subscriptions', methods=['GET'])
<li class="sidebar-list-item"> def get_subscriptions_page():
<span class="sidebar-item-name">$tag_name</span>
<form method="POST" class="sidebar-item-refresh">
<input type="submit" value="Check">
<input type="hidden" name="action" value="refresh">
<input type="hidden" name="type" value="tag">
<input type="hidden" name="tag_name" value="$tag_name">
</form>
</li>''')
sidebar_channel_item_template = Template('''
<li class="sidebar-list-item $mute_class">
<a href="$channel_url" class="sidebar-item-name" title="$channel_name">$channel_name</a>
<form method="POST" class="sidebar-item-refresh">
<input type="submit" value="Check">
<input type="hidden" name="action" value="refresh">
<input type="hidden" name="type" value="channel">
<input type="hidden" name="channel_id" value="$channel_id">
</form>
</li>''')
def get_subscriptions_page(env, start_response):
with open_database() as connection: with open_database() as connection:
with connection as cursor: with connection as cursor:
items_html = '''<nav class="item-grid">\n''' videos = []
for video in _get_videos(cursor, 60, 0):
for item in _get_videos(cursor, 60, 0): if video['id'] in downloading_thumbnails:
if item['id'] in downloading_thumbnails: video['thumbnail'] = util.get_thumbnail_url(video['id'])
item['thumbnail'] = util.get_thumbnail_url(item['id'])
else: else:
item['thumbnail'] = util.URL_ORIGIN + '/data/subscription_thumbnails/' + item['id'] + '.jpg' video['thumbnail'] = util.URL_ORIGIN + '/data/subscription_thumbnails/' + video['id'] + '.jpg'
items_html += html_common.video_item_html(item, html_common.small_video_item_template) video['type'] = 'video'
items_html += '''\n</nav>''' video['item_size'] = 'small'
videos.append(video)
tags = _get_all_tags(cursor)
tag_list_html = '' subscription_list = []
for tag_name in _get_all_tags(cursor):
tag_list_html += sidebar_tag_item_template.substitute(tag_name = tag_name)
sub_list_html = ''
for channel_name, channel_id, muted in _get_subscribed_channels(cursor): for channel_name, channel_id, muted in _get_subscribed_channels(cursor):
sub_list_html += sidebar_channel_item_template.substitute( subscription_list.append({
channel_url = util.URL_ORIGIN + '/channel/' + channel_id, 'channel_url': util.URL_ORIGIN + '/channel/' + channel_id,
channel_name = html.escape(channel_name), 'channel_name': channel_name,
channel_id = channel_id, 'channel_id': channel_id,
mute_class = 'muted' if muted else '', 'muted': muted,
) })
return flask.render_template('subscriptions.html',
videos = videos,
tags = tags,
subscription_list = subscription_list,
)
@yt_app.route('/subscriptions', methods=['POST'])
start_response('200 OK', [('Content-type','text/html'),]) @yt_app.route('/feed/subscriptions', methods=['POST'])
return subscriptions_template.substitute( def post_subscriptions_page():
header = html_common.get_header(), action = request.values['action']
items = items_html,
tags = tag_list_html,
sub_list = sub_list_html,
page_buttons = '',
).encode('utf-8')
def post_subscriptions_page(env, start_response):
params = env['parameters']
action = params['action'][0]
if action == 'subscribe': if action == 'subscribe':
if len(params['channel_id']) != len(params['channel_name']): if len(request.values.getlist('channel_id')) != len(request.values('channel_name')):
start_response('400 Bad Request', ()) return '400 Bad Request, length of channel_id != length of channel_name', 400
return b'400 Bad Request, length of channel_id != length of channel_name' with_open_db(_subscribe, zip(request.values.getlist('channel_id'), request.values.getlist('channel_name')))
with_open_db(_subscribe, zip(params['channel_id'], params['channel_name']))
elif action == 'unsubscribe': elif action == 'unsubscribe':
with_open_db(_unsubscribe, params['channel_id']) with_open_db(_unsubscribe, request.values.getlist('channel_id'))
elif action == 'refresh': elif action == 'refresh':
type = params['type'][0] type = request.values['type']
if type == 'all': if type == 'all':
check_all_channels() check_all_channels()
elif type == 'tag': elif type == 'tag':
check_tags(params['tag_name']) check_tags(request.values.getlist('tag_name'))
elif type == 'channel': elif type == 'channel':
check_specific_channels(params['channel_id']) check_specific_channels(request.values.getlist('channel_id'))
else: else:
start_response('400 Bad Request', ()) flask.abort(400)
return b'400 Bad Request'
start_response('204 No Content', ())
return b''
else: else:
start_response('400 Bad Request', ()) flask.abort(400)
return b'400 Bad Request'
start_response('204 No Content', ()) return '', 204
return b''
@yt_app.route('/data/subscription_thumbnails/<thumbnail>')
def serve_subscription_thumbnail(thumbnail):
# .. is necessary because flask always uses the application directory at ./youtube, not the working directory
return flask.send_from_directory(os.path.join('..', thumbnails_directory), thumbnail)

View File

@ -0,0 +1,97 @@
{% set page_title = 'Subscriptions' %}
{% extends "base.html" %}
{% import "common_elements.html" as common_elements %}
{% block style %}
main{
display:flex;
flex-direction: row;
}
.item-grid{
flex-grow: 1;
}
.subscriptions-sidebar{
flex-basis: 300px;
background-color: #dadada;
border-left: 2px;
}
.sidebar-links{
display:flex;
justify-content: space-between;
padding-left:10px;
padding-right: 10px;
}
.sidebar-list{
list-style: none;
padding-left:10px;
padding-right: 10px;
}
.sidebar-list-item{
display:flex;
justify-content: space-between;
margin-bottom: 5px;
}
.sub-refresh-list .sidebar-item-name{
text-overflow: clip;
white-space: nowrap;
overflow: hidden;
max-width: 200px;
}
.muted{
background-color: #888888;
}
{% endblock style %}
{% block main %}
<nav class="item-grid">
{% for video_info in videos %}
{{ common_elements.item(video_info, include_author=false) }}
{% endfor %}
</nav>
<div class="subscriptions-sidebar">
<div class="sidebar-links">
<a href="/youtube.com/subscription_manager" class="sub-manager-link">Subscription Manager</a>
<form method="POST" class="refresh-all">
<input type="submit" value="Check All">
<input type="hidden" name="action" value="refresh">
<input type="hidden" name="type" value="all">
</form>
</div>
<hr>
<ol class="sidebar-list tags">
{% for tag in tags %}
<li class="sidebar-list-item">
<span class="sidebar-item-name">{{ tag }}</span>
<form method="POST" class="sidebar-item-refresh">
<input type="submit" value="Check">
<input type="hidden" name="action" value="refresh">
<input type="hidden" name="type" value="tag">
<input type="hidden" name="tag_name" value="{{ tag }}">
</form>
</li>
{% endfor %}
</ol>
<ol class="sidebar-list sub-refresh-list">
{% for subscription in subscription_list %}
<li class="sidebar-list-item {{ 'muted' if subscription['muted'] else '' }}">
<a href="{{ subscription['channel_url'] }}" class="sidebar-item-name" title="{{ subscription['channel_name'] }}">{{ subscription['channel_name'] }}</a>
<form method="POST" class="sidebar-item-refresh">
<input type="submit" value="Check">
<input type="hidden" name="action" value="refresh">
<input type="hidden" name="type" value="channel">
<input type="hidden" name="channel_id" value="{{ subscription['channel_id'] }}">
</form>
</li>
{% endfor %}
</ol>
</div>
<nav class="page-button-row">
{# TODO #}
</nav>
{% endblock main %}

View File

@ -1,82 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Subscriptions</title>
<link href="/youtube.com/shared.css" type="text/css" rel="stylesheet">
<link href="/youtube.com/favicon.ico" type="image/x-icon" rel="icon">
<link title="Youtube local" href="/youtube.com/opensearch.xml" rel="search" type="application/opensearchdescription+xml">
<style type="text/css">
main{
display:flex;
flex-direction: row;
}
.item-grid{
flex-grow: 1;
}
.subscriptions-sidebar{
flex-basis: 300px;
background-color: #dadada;
border-left: 2px;
}
.sidebar-links{
display:flex;
justify-content: space-between;
padding-left:10px;
padding-right: 10px;
}
.sidebar-list{
list-style: none;
padding-left:10px;
padding-right: 10px;
}
.sidebar-list-item{
display:flex;
justify-content: space-between;
margin-bottom: 5px;
}
.sub-refresh-list .sidebar-item-name{
text-overflow: clip;
white-space: nowrap;
overflow: hidden;
max-width: 200px;
}
.muted{
background-color: #888888;
}
</style>
</head>
<body>
$header
<main>
$items
<div class="subscriptions-sidebar">
<div class="sidebar-links">
<a href="/youtube.com/subscription_manager" class="sub-manager-link">Subscription Manager</a>
<form method="POST" class="refresh-all">
<input type="submit" value="Check All">
<input type="hidden" name="action" value="refresh">
<input type="hidden" name="type" value="all">
</form>
</div>
<hr>
<ol class="sidebar-list tags">
$tags
</ol>
<ol class="sidebar-list sub-refresh-list">
$sub_list
</ol>
</div>
<nav class="page-button-row">
$page_buttons
</nav>
</main>
</body>
</html>