Youtube: Video page + improved search
This commit is contained in:
parent
f717f9bc91
commit
40a01beb58
@ -1 +1 @@
|
|||||||
{"twitter": [{"username": "aantonop"}, {"username": "Trezor"}, {"username": "aitor13023985"}, {"username": "guaridadelzorro"}, {"username": "elonmusk"}, {"username": "bitcoin"}, {"username": "monero"}, {"username": "xmroutreach"}, {"username": "Locha_io"}, {"username": "randybrito"}, {"username": "btcven"}, {"username": "Snowden"}, {"username": "_prestwich"}, {"username": "TradeOnFire"}], "youtube": [{"channelId": "UCjr2bPAyPV7t35MvcgT3W8Q"}, {"channelId": "UCJWCJCWOxBYSi5DhCieLOLQ"}, {"channelId": "UCW3iqZr2cQFYKdO9Kpa97Yw"}, {"channelId": "UCLXo7UDZvByw2ixzpQCufnA"}, {"channelId": "UCR9sFzaG9Ia_kXJhfxtFMBA"}, {"channelId": "UCbdSYaPD-lr1kW27UJuk8Pw"}, {"channelId": "UCa3DVlGH2_QhvwuWlPa6MDQ"}, {"channelId": "UComoqWnjQlH7JpvqfyGnWNA"}, {"channelId": "UCA58hhLv4pdgDjBW5oNSklA"}, {"channelId": "UCkHR9m-tscD3ojD7_viIfTA"}, {"channelId": "UCYO_jab_esuFRV4b17AJtAw"}, {"channelId": "UC9-y-6csu5WGm29I7JiwpnA"}, {"channelId": "UC2PA-AKmVpU6NKCGtZq_rKQ"}, {"channelId": "UC2Qc22WUjL_GbBDIPvmHWmQ"}, {"channelId": "UC3Hx81QYLoEQkm3vyl4N4eQ"}, {"channelId": "UCiwy9Lx6h1Oi-UYwYDbgBZA"}, {"channelId": "UCHW_zn-nX5nvMjEA6mtNZ1A"}]}
|
{"twitter": [{"username": "aantonop"}, {"username": "Trezor"}, {"username": "aitor13023985"}, {"username": "guaridadelzorro"}, {"username": "elonmusk"}, {"username": "bitcoin"}, {"username": "monero"}, {"username": "xmroutreach"}, {"username": "Locha_io"}, {"username": "randybrito"}, {"username": "btcven"}, {"username": "Snowden"}, {"username": "_prestwich"}, {"username": "TradeOnFire"}, {"username": "fluffypony"}, {"username": "browseyourlife"}], "youtube": [{"channelId": "UCjr2bPAyPV7t35MvcgT3W8Q"}, {"channelId": "UCJWCJCWOxBYSi5DhCieLOLQ"}, {"channelId": "UCW3iqZr2cQFYKdO9Kpa97Yw"}, {"channelId": "UCLXo7UDZvByw2ixzpQCufnA"}, {"channelId": "UCR9sFzaG9Ia_kXJhfxtFMBA"}, {"channelId": "UCbdSYaPD-lr1kW27UJuk8Pw"}, {"channelId": "UCa3DVlGH2_QhvwuWlPa6MDQ"}, {"channelId": "UComoqWnjQlH7JpvqfyGnWNA"}, {"channelId": "UCA58hhLv4pdgDjBW5oNSklA"}, {"channelId": "UCkHR9m-tscD3ojD7_viIfTA"}, {"channelId": "UCYO_jab_esuFRV4b17AJtAw"}, {"channelId": "UC9-y-6csu5WGm29I7JiwpnA"}, {"channelId": "UC2PA-AKmVpU6NKCGtZq_rKQ"}, {"channelId": "UC2Qc22WUjL_GbBDIPvmHWmQ"}, {"channelId": "UC3Hx81QYLoEQkm3vyl4N4eQ"}, {"channelId": "UCiwy9Lx6h1Oi-UYwYDbgBZA"}, {"channelId": "UCHW_zn-nX5nvMjEA6mtNZ1A"}, {"channelId": "UCy5znSnfMsDwaLlROnZ7Qbg"}, {"channelId": "UCJQQVLyM6wtPleV4wFBK06g"}, {"channelId": "UCyoAJbq6rqi9bgMB0uDQSFg"}, {"channelId": "UCsXVk37bltHxD1rDPwtNM8Q"}]}
|
@ -102,6 +102,7 @@ class invidiousPost():
|
|||||||
description = "LOREM IPSUM"
|
description = "LOREM IPSUM"
|
||||||
date = 'None'
|
date = 'None'
|
||||||
views = 'NaN'
|
views = 'NaN'
|
||||||
|
id = 'isod'
|
||||||
|
|
||||||
|
|
||||||
class invidiousFollow(db.Model):
|
class invidiousFollow(db.Model):
|
||||||
|
407
app/routes.py
407
app/routes.py
@ -1,4 +1,4 @@
|
|||||||
from flask import render_template, flash, redirect, url_for, request, send_from_directory
|
from flask import render_template, flash, redirect, url_for, request, send_from_directory, Markup
|
||||||
from app.forms import LoginForm, RegistrationForm, EmptyForm, SearchForm, ChannelForm
|
from app.forms import LoginForm, RegistrationForm, EmptyForm, SearchForm, ChannelForm
|
||||||
from app.models import User, twitterPost, invidiousPost, Post, invidiousFollow
|
from app.models import User, twitterPost, invidiousPost, Post, invidiousFollow
|
||||||
from flask_login import login_user, logout_user, current_user, login_required
|
from flask_login import login_user, logout_user, current_user, login_required
|
||||||
@ -6,23 +6,27 @@ from requests_futures.sessions import FuturesSession
|
|||||||
from concurrent.futures import as_completed
|
from concurrent.futures import as_completed
|
||||||
from werkzeug.urls import url_parse
|
from werkzeug.urls import url_parse
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from flask import Markup
|
|
||||||
from app import app, db
|
from app import app, db
|
||||||
import time, datetime
|
|
||||||
import random, string
|
import random, string
|
||||||
|
import time, datetime
|
||||||
import feedparser
|
import feedparser
|
||||||
import requests
|
import requests
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
# Instances - Format must be instance.tld (No '/' and no 'https://')
|
||||||
nitterInstance = "https://nitter.net/"
|
nitterInstance = "https://nitter.net/"
|
||||||
nitterInstanceII = "https://nitter.mastodont.cat"
|
nitterInstanceII = "https://nitter.mastodont.cat/"
|
||||||
|
invidiousInstance = "invidious.snopyta.org"
|
||||||
|
|
||||||
|
#########################
|
||||||
|
#### Twitter Logic ######
|
||||||
|
#########################
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
@app.route('/index')
|
@app.route('/index')
|
||||||
@login_required
|
@login_required
|
||||||
def index():
|
def index():
|
||||||
#start_time = time.time()
|
start_time = time.time()
|
||||||
following = current_user.following_list()
|
following = current_user.following_list()
|
||||||
followed = current_user.followed.count()
|
followed = current_user.followed.count()
|
||||||
posts = []
|
posts = []
|
||||||
@ -34,141 +38,9 @@ def index():
|
|||||||
profilePic = avatarPath
|
profilePic = avatarPath
|
||||||
else:
|
else:
|
||||||
profilePic = posts[0].userProfilePic
|
profilePic = posts[0].userProfilePic
|
||||||
#print("--- {} seconds fetching feed---".format(time.time() - start_time))
|
print("--- {} seconds fetching twitter feed---".format(time.time() - start_time))
|
||||||
return render_template('index.html', title='Home', posts=posts, avatar=avatarPath, profilePic = profilePic, followedCount=followed, form=form)
|
return render_template('index.html', title='Home', posts=posts, avatar=avatarPath, profilePic = profilePic, followedCount=followed, form=form)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/login', methods=['GET', 'POST'])
|
|
||||||
def login():
|
|
||||||
if current_user.is_authenticated:
|
|
||||||
return redirect(url_for('index'))
|
|
||||||
form = LoginForm()
|
|
||||||
if form.validate_on_submit():
|
|
||||||
user = User.query.filter_by(username=form.username.data).first()
|
|
||||||
if user is None or not user.check_password(form.password.data):
|
|
||||||
flash('Invalid username or password')
|
|
||||||
return redirect(url_for('login'))
|
|
||||||
login_user(user, remember=form.remember_me.data)
|
|
||||||
next_page = request.args.get('next')
|
|
||||||
if not next_page or url_parse(next_page).netloc != '':
|
|
||||||
next_page = url_for('index')
|
|
||||||
return redirect(next_page)
|
|
||||||
return render_template('login.html', title='Sign In', form=form)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/logout')
|
|
||||||
def logout():
|
|
||||||
logout_user()
|
|
||||||
return redirect(url_for('index'))
|
|
||||||
|
|
||||||
@app.route('/settings')
|
|
||||||
@login_required
|
|
||||||
def settings():
|
|
||||||
return render_template('settings.html')
|
|
||||||
|
|
||||||
@app.route('/export')
|
|
||||||
@login_required
|
|
||||||
#Export data into a JSON file. Later you can import the data.
|
|
||||||
def export():
|
|
||||||
a = exportData()
|
|
||||||
if a:
|
|
||||||
return send_from_directory('.', 'data_export.json', as_attachment=True)
|
|
||||||
else:
|
|
||||||
return redirect(url_for('error/405'))
|
|
||||||
|
|
||||||
def exportData():
|
|
||||||
twitterFollowing = current_user.following_list()
|
|
||||||
youtubeFollowing = current_user.youtube_following_list()
|
|
||||||
data = {}
|
|
||||||
data['twitter'] = []
|
|
||||||
data['youtube'] = []
|
|
||||||
|
|
||||||
for f in twitterFollowing:
|
|
||||||
data['twitter'].append({
|
|
||||||
'username': f.username
|
|
||||||
})
|
|
||||||
|
|
||||||
for f in youtubeFollowing:
|
|
||||||
data['youtube'].append({
|
|
||||||
'channelId': f.channelId
|
|
||||||
})
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open('app/data_export.json', 'w') as outfile:
|
|
||||||
json.dump(data, outfile)
|
|
||||||
return True
|
|
||||||
except:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/register', methods=['GET', 'POST'])
|
|
||||||
def register():
|
|
||||||
if current_user.is_authenticated:
|
|
||||||
return redirect(url_for('index'))
|
|
||||||
|
|
||||||
form = RegistrationForm()
|
|
||||||
if form.validate_on_submit():
|
|
||||||
if isTwitterUser(form.username.data):
|
|
||||||
flash('This is username is taken! Choose a different one.')
|
|
||||||
else:
|
|
||||||
user = User(username=form.username.data, email=form.email.data)
|
|
||||||
user.set_password(form.password.data)
|
|
||||||
db.session.add(user)
|
|
||||||
db.session.commit()
|
|
||||||
flash('Congratulations, you are now a registered user!')
|
|
||||||
return redirect(url_for('login'))
|
|
||||||
return render_template('register.html', title='Register', form=form)
|
|
||||||
|
|
||||||
@app.route('/invidious', methods=['GET', 'POST'])
|
|
||||||
@login_required
|
|
||||||
def invidious():
|
|
||||||
form = ChannelForm()
|
|
||||||
if form.validate_on_submit():
|
|
||||||
channelId = form.channelId.data
|
|
||||||
if requests.get('https://invidio.us/feed/channel/{}'.format(channelId)).status_code == 200:
|
|
||||||
follow = invidiousFollow()
|
|
||||||
follow.channelId = channelId
|
|
||||||
follow.followers.append(current_user)
|
|
||||||
try:
|
|
||||||
db.session.add(follow)
|
|
||||||
db.session.commit()
|
|
||||||
flash("Added to list!")
|
|
||||||
except:
|
|
||||||
flash("Something went wrong. Try again!")
|
|
||||||
return redirect(url_for('invidious'))
|
|
||||||
else:
|
|
||||||
flash("Enter a valid Channel ID. Eg: UCJWCJCWOxBYSi5DhCieLOLQ")
|
|
||||||
return redirect(url_for('invidious'))
|
|
||||||
ids = current_user.youtube_following_list()
|
|
||||||
videos = getInvidiousPosts(ids)
|
|
||||||
if videos:
|
|
||||||
videos.sort(key=lambda x: x.date, reverse=True)
|
|
||||||
return render_template('invidious.html', videos=videos, form=form)
|
|
||||||
|
|
||||||
@app.route('/ytsearch', methods=['GET', 'POST'])
|
|
||||||
@login_required
|
|
||||||
def ytsearch():
|
|
||||||
form = ChannelForm()
|
|
||||||
button_form = EmptyForm()
|
|
||||||
if form.validate_on_submit():
|
|
||||||
channelId = form.channelId.data
|
|
||||||
r = requests.get('https://invidio.us/api/v1/search?type=channel&q={}'.format(channelId))
|
|
||||||
if r.status_code == 200:
|
|
||||||
results = json.loads(r.content)
|
|
||||||
channels = []
|
|
||||||
for res in results:
|
|
||||||
channels.append({
|
|
||||||
'username':res['author'],
|
|
||||||
'channelId':res['authorId'],
|
|
||||||
'thumbnail':res['authorThumbnails'][0]['url'],
|
|
||||||
'subCount':letterify(res['subCount'])
|
|
||||||
})
|
|
||||||
|
|
||||||
return render_template('ytsearch.html', form=form, btform=button_form, results=channels)
|
|
||||||
else:
|
|
||||||
return render_template('ytsearch.html', form=form)
|
|
||||||
|
|
||||||
@app.route('/savePost/<url>', methods=['POST'])
|
@app.route('/savePost/<url>', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def savePost(url):
|
def savePost(url):
|
||||||
@ -204,43 +76,6 @@ def deleteSaved(id):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
return redirect(url_for('saved'))
|
return redirect(url_for('saved'))
|
||||||
|
|
||||||
@app.route('/ytfollow/<channelId>', methods=['POST'])
|
|
||||||
@login_required
|
|
||||||
def ytfollow(channelId):
|
|
||||||
form = EmptyForm()
|
|
||||||
if form.validate_on_submit():
|
|
||||||
channel = invidiousFollow.query.filter_by(channelId=channelId).first()
|
|
||||||
if requests.get('https://invidio.us/feed/channel/{}'.format(channelId)).status_code == 200:
|
|
||||||
if channel is None:
|
|
||||||
follow = invidiousFollow()
|
|
||||||
follow.channelId = channelId
|
|
||||||
follow.followers.append(current_user)
|
|
||||||
try:
|
|
||||||
db.session.add(follow)
|
|
||||||
db.session.commit()
|
|
||||||
except:
|
|
||||||
flash("Something went wrong. Try again!")
|
|
||||||
return redirect(url_for('invidious'))
|
|
||||||
flash('You are following {}!'.format(channelId))
|
|
||||||
else:
|
|
||||||
flash("Something went wrong... try again")
|
|
||||||
return redirect(url_for('ytsearch'))
|
|
||||||
else:
|
|
||||||
return redirect(url_for('ytsearch'))
|
|
||||||
|
|
||||||
@app.route('/ytunfollow/<channelId>', methods=['POST'])
|
|
||||||
@login_required
|
|
||||||
def ytunfollow(channelId):
|
|
||||||
form = EmptyForm()
|
|
||||||
channel = invidiousFollow.query.filter_by(channelId=channelId).first()
|
|
||||||
try:
|
|
||||||
db.session.delete(channel)
|
|
||||||
db.session.commit()
|
|
||||||
flash("User unfollowed!")
|
|
||||||
except:
|
|
||||||
flash("There was an error unfollowing the user. Try again.")
|
|
||||||
return redirect(url_for('ytsearch'))
|
|
||||||
|
|
||||||
@app.route('/follow/<username>', methods=['POST'])
|
@app.route('/follow/<username>', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def follow(username):
|
def follow(username):
|
||||||
@ -331,12 +166,6 @@ def search():
|
|||||||
else:
|
else:
|
||||||
return render_template('search.html', form = form)
|
return render_template('search.html', form = form)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/error/<errno>')
|
|
||||||
def error(errno):
|
|
||||||
return render_template('{}.html'.format(str(errno)))
|
|
||||||
|
|
||||||
@app.route('/user/<username>')
|
@app.route('/user/<username>')
|
||||||
@login_required
|
@login_required
|
||||||
def user(username):
|
def user(username):
|
||||||
@ -362,6 +191,219 @@ def user(username):
|
|||||||
profilePic = posts[0].userProfilePic
|
profilePic = posts[0].userProfilePic
|
||||||
return render_template('user.html', user=user, posts=posts, profilePic = profilePic, form=form)
|
return render_template('user.html', user=user, posts=posts, profilePic = profilePic, form=form)
|
||||||
|
|
||||||
|
#########################
|
||||||
|
#### Youtube Logic ######
|
||||||
|
#########################
|
||||||
|
@app.route('/invidious', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
def invidious():
|
||||||
|
start_time = time.time()
|
||||||
|
form = ChannelForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
channelId = form.channelId.data
|
||||||
|
if requests.get('https://{instance}/feed/channel/{cid}'.format(instance=invidiousInstance, cid=channelId)).status_code == 200:
|
||||||
|
follow = invidiousFollow()
|
||||||
|
follow.channelId = channelId
|
||||||
|
follow.followers.append(current_user)
|
||||||
|
try:
|
||||||
|
db.session.add(follow)
|
||||||
|
db.session.commit()
|
||||||
|
flash("Added to list!")
|
||||||
|
except:
|
||||||
|
flash("Something went wrong. Try again!")
|
||||||
|
return redirect(url_for('invidious'))
|
||||||
|
else:
|
||||||
|
flash("Enter a valid Channel ID. Eg: UCJWCJCWOxBYSi5DhCieLOLQ")
|
||||||
|
return redirect(url_for('invidious'))
|
||||||
|
ids = current_user.youtube_following_list()
|
||||||
|
videos = getInvidiousPosts(ids)
|
||||||
|
if videos:
|
||||||
|
videos.sort(key=lambda x: x.date, reverse=True)
|
||||||
|
print("--- {} seconds fetching invidious feed---".format(time.time() - start_time))
|
||||||
|
return render_template('invidious.html', videos=videos, form=form)
|
||||||
|
|
||||||
|
@app.route('/ytsearch', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
def ytsearch():
|
||||||
|
form = ChannelForm()
|
||||||
|
button_form = EmptyForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
channelId = form.channelId.data
|
||||||
|
c = requests.get('https://{instance}/api/v1/search?type=channel&q={cid}'.format(instance=invidiousInstance, cid=channelId))
|
||||||
|
v = requests.get('https://{instance}/api/v1/search?type=video&q={cid}'.format(instance=invidiousInstance, cid=channelId))
|
||||||
|
if c.status_code == 200 and v.status_code == 200:
|
||||||
|
results = json.loads(c.content)
|
||||||
|
channels = []
|
||||||
|
videos = []
|
||||||
|
for res in results:
|
||||||
|
channels.append({
|
||||||
|
'username':res['author'],
|
||||||
|
'channelId':res['authorId'],
|
||||||
|
'thumbnail':res['authorThumbnails'][0]['url'],
|
||||||
|
'subCount':letterify(res['subCount'])
|
||||||
|
})
|
||||||
|
|
||||||
|
results = json.loads(v.content)
|
||||||
|
for data in results:
|
||||||
|
videos.append({
|
||||||
|
'instance':invidiousInstance,
|
||||||
|
'author':data['author'],
|
||||||
|
'videoTitle':data['title'],
|
||||||
|
'description':Markup(data['description'][0:125]+'...'),
|
||||||
|
'id':data['videoId'],
|
||||||
|
'videoThumb': data['videoThumbnails'][4]['url'],
|
||||||
|
'channelUrl':data['authorUrl'],
|
||||||
|
'views':data['viewCount'],
|
||||||
|
'timeStamp':data['publishedText']
|
||||||
|
})
|
||||||
|
|
||||||
|
return render_template('ytsearch.html', form=form, btform=button_form, results=channels, videos=videos)
|
||||||
|
else:
|
||||||
|
return render_template('ytsearch.html', form=form)
|
||||||
|
|
||||||
|
@app.route('/ytfollow/<channelId>', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def ytfollow(channelId):
|
||||||
|
form = EmptyForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
channel = invidiousFollow.query.filter_by(channelId=channelId).first()
|
||||||
|
if requests.get('https://{instance}/feed/channel/{cid}'.format(instance=invidiousInstance, cid=channelId)).status_code == 200:
|
||||||
|
if channel is None:
|
||||||
|
follow = invidiousFollow()
|
||||||
|
follow.channelId = channelId
|
||||||
|
follow.followers.append(current_user)
|
||||||
|
try:
|
||||||
|
db.session.add(follow)
|
||||||
|
db.session.commit()
|
||||||
|
except:
|
||||||
|
flash("Something went wrong. Try again!")
|
||||||
|
return redirect(url_for('invidious'))
|
||||||
|
flash('You are following {}!'.format(channelId))
|
||||||
|
else:
|
||||||
|
flash("Something went wrong... try again")
|
||||||
|
return redirect(url_for('ytsearch'))
|
||||||
|
else:
|
||||||
|
return redirect(url_for('ytsearch'))
|
||||||
|
|
||||||
|
@app.route('/ytunfollow/<channelId>', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def ytunfollow(channelId):
|
||||||
|
form = EmptyForm()
|
||||||
|
channel = invidiousFollow.query.filter_by(channelId=channelId).first()
|
||||||
|
try:
|
||||||
|
db.session.delete(channel)
|
||||||
|
db.session.commit()
|
||||||
|
flash("User unfollowed!")
|
||||||
|
except:
|
||||||
|
flash("There was an error unfollowing the user. Try again.")
|
||||||
|
return redirect(url_for('ytsearch'))
|
||||||
|
|
||||||
|
@app.route('/video/<id>', methods=['POST', 'GET'])
|
||||||
|
@login_required
|
||||||
|
def video(id):
|
||||||
|
data = requests.get('https://{instance}/api/v1/videos/{id}'.format(instance=invidiousInstance, id=id))
|
||||||
|
data = json.loads(data.content)
|
||||||
|
|
||||||
|
video = {
|
||||||
|
'title':data['title'],
|
||||||
|
'description':Markup(data['descriptionHtml']),
|
||||||
|
'viewCount':data['viewCount'],
|
||||||
|
'likeCount':data['likeCount'],
|
||||||
|
'dislikeCount':data['dislikeCount'],
|
||||||
|
'authorThumb':data['authorThumbnails'][4]['url'],
|
||||||
|
'author':data['author'],
|
||||||
|
'authorUrl':data['authorUrl'],
|
||||||
|
'instance':invidiousInstance,
|
||||||
|
'id':id
|
||||||
|
}
|
||||||
|
return render_template("video.html", video=video)
|
||||||
|
|
||||||
|
#########################
|
||||||
|
#### General Logic ######
|
||||||
|
#########################
|
||||||
|
@app.route('/login', methods=['GET', 'POST'])
|
||||||
|
def login():
|
||||||
|
if current_user.is_authenticated:
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
form = LoginForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
user = User.query.filter_by(username=form.username.data).first()
|
||||||
|
if user is None or not user.check_password(form.password.data):
|
||||||
|
flash('Invalid username or password')
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
login_user(user, remember=form.remember_me.data)
|
||||||
|
next_page = request.args.get('next')
|
||||||
|
if not next_page or url_parse(next_page).netloc != '':
|
||||||
|
next_page = url_for('index')
|
||||||
|
return redirect(next_page)
|
||||||
|
return render_template('login.html', title='Sign In', form=form)
|
||||||
|
|
||||||
|
@app.route('/logout')
|
||||||
|
def logout():
|
||||||
|
logout_user()
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
|
@app.route('/settings')
|
||||||
|
@login_required
|
||||||
|
def settings():
|
||||||
|
return render_template('settings.html')
|
||||||
|
|
||||||
|
@app.route('/export')
|
||||||
|
@login_required
|
||||||
|
#Export data into a JSON file. Later you can import the data.
|
||||||
|
def export():
|
||||||
|
a = exportData()
|
||||||
|
if a:
|
||||||
|
return send_from_directory('.', 'data_export.json', as_attachment=True)
|
||||||
|
else:
|
||||||
|
return redirect(url_for('error/405'))
|
||||||
|
|
||||||
|
def exportData():
|
||||||
|
twitterFollowing = current_user.following_list()
|
||||||
|
youtubeFollowing = current_user.youtube_following_list()
|
||||||
|
data = {}
|
||||||
|
data['twitter'] = []
|
||||||
|
data['youtube'] = []
|
||||||
|
|
||||||
|
for f in twitterFollowing:
|
||||||
|
data['twitter'].append({
|
||||||
|
'username': f.username
|
||||||
|
})
|
||||||
|
|
||||||
|
for f in youtubeFollowing:
|
||||||
|
data['youtube'].append({
|
||||||
|
'channelId': f.channelId
|
||||||
|
})
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open('app/data_export.json', 'w') as outfile:
|
||||||
|
json.dump(data, outfile)
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@app.route('/register', methods=['GET', 'POST'])
|
||||||
|
def register():
|
||||||
|
if current_user.is_authenticated:
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
|
form = RegistrationForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
if isTwitterUser(form.username.data):
|
||||||
|
flash('This is username is taken! Choose a different one.')
|
||||||
|
else:
|
||||||
|
user = User(username=form.username.data, email=form.email.data)
|
||||||
|
user.set_password(form.password.data)
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.commit()
|
||||||
|
flash('Congratulations, you are now a registered user!')
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
return render_template('register.html', title='Register', form=form)
|
||||||
|
|
||||||
|
@app.route('/error/<errno>')
|
||||||
|
def error(errno):
|
||||||
|
return render_template('{}.html'.format(str(errno)))
|
||||||
|
|
||||||
def getTimeDiff(t):
|
def getTimeDiff(t):
|
||||||
tweetTime = datetime.datetime(*t[:6])
|
tweetTime = datetime.datetime(*t[:6])
|
||||||
diff = datetime.datetime.now() - tweetTime
|
diff = datetime.datetime.now() - tweetTime
|
||||||
@ -459,7 +501,7 @@ def getPosts(account):
|
|||||||
def getInvidiousPosts(ids):
|
def getInvidiousPosts(ids):
|
||||||
videos = []
|
videos = []
|
||||||
with FuturesSession() as session:
|
with FuturesSession() as session:
|
||||||
futures = [session.get('https://invidio.us/feed/channel/{}'.format(id.channelId)) for id in ids]
|
futures = [session.get('https://{instance}/feed/channel/{id}'.format(instance=invidiousInstance, id=id.channelId)) for id in ids]
|
||||||
for future in as_completed(futures):
|
for future in as_completed(futures):
|
||||||
resp = future.result()
|
resp = future.result()
|
||||||
rssFeed=feedparser.parse(resp.content)
|
rssFeed=feedparser.parse(resp.content)
|
||||||
@ -470,6 +512,7 @@ def getInvidiousPosts(ids):
|
|||||||
video.channelName = vid.author_detail.name
|
video.channelName = vid.author_detail.name
|
||||||
video.channelUrl = vid.author_detail.href
|
video.channelUrl = vid.author_detail.href
|
||||||
video.videoUrl = vid.link
|
video.videoUrl = vid.link
|
||||||
|
video.id = vid.link.split("?v=")[1]
|
||||||
video.videoTitle = vid.title
|
video.videoTitle = vid.title
|
||||||
video.videoThumb = vid.media_thumbnail[0]['url']
|
video.videoThumb = vid.media_thumbnail[0]['url']
|
||||||
video.views = vid.media_statistics['views']
|
video.views = vid.media_statistics['views']
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
app/static/semantic/fonts/Mulish-Black.ttf
Normal file
BIN
app/static/semantic/fonts/Mulish-Black.ttf
Normal file
Binary file not shown.
BIN
app/static/semantic/fonts/Mulish-BlackItalic.ttf
Normal file
BIN
app/static/semantic/fonts/Mulish-BlackItalic.ttf
Normal file
Binary file not shown.
BIN
app/static/semantic/fonts/Mulish-Bold.ttf
Normal file
BIN
app/static/semantic/fonts/Mulish-Bold.ttf
Normal file
Binary file not shown.
BIN
app/static/semantic/fonts/Mulish-BoldItalic.ttf
Normal file
BIN
app/static/semantic/fonts/Mulish-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
app/static/semantic/fonts/Mulish-ExtraBold.ttf
Normal file
BIN
app/static/semantic/fonts/Mulish-ExtraBold.ttf
Normal file
Binary file not shown.
BIN
app/static/semantic/fonts/Mulish-ExtraBoldItalic.ttf
Normal file
BIN
app/static/semantic/fonts/Mulish-ExtraBoldItalic.ttf
Normal file
Binary file not shown.
BIN
app/static/semantic/fonts/Mulish-ExtraLight.ttf
Normal file
BIN
app/static/semantic/fonts/Mulish-ExtraLight.ttf
Normal file
Binary file not shown.
BIN
app/static/semantic/fonts/Mulish-ExtraLightItalic.ttf
Normal file
BIN
app/static/semantic/fonts/Mulish-ExtraLightItalic.ttf
Normal file
Binary file not shown.
BIN
app/static/semantic/fonts/Mulish-Italic.ttf
Normal file
BIN
app/static/semantic/fonts/Mulish-Italic.ttf
Normal file
Binary file not shown.
BIN
app/static/semantic/fonts/Mulish-Light.ttf
Normal file
BIN
app/static/semantic/fonts/Mulish-Light.ttf
Normal file
Binary file not shown.
BIN
app/static/semantic/fonts/Mulish-LightItalic.ttf
Normal file
BIN
app/static/semantic/fonts/Mulish-LightItalic.ttf
Normal file
Binary file not shown.
BIN
app/static/semantic/fonts/Mulish-Medium.ttf
Normal file
BIN
app/static/semantic/fonts/Mulish-Medium.ttf
Normal file
Binary file not shown.
BIN
app/static/semantic/fonts/Mulish-MediumItalic.ttf
Normal file
BIN
app/static/semantic/fonts/Mulish-MediumItalic.ttf
Normal file
Binary file not shown.
BIN
app/static/semantic/fonts/Mulish-Regular.ttf
Normal file
BIN
app/static/semantic/fonts/Mulish-Regular.ttf
Normal file
Binary file not shown.
BIN
app/static/semantic/fonts/Mulish-SemiBold.ttf
Normal file
BIN
app/static/semantic/fonts/Mulish-SemiBold.ttf
Normal file
Binary file not shown.
BIN
app/static/semantic/fonts/Mulish-SemiBoldItalic.ttf
Normal file
BIN
app/static/semantic/fonts/Mulish-SemiBoldItalic.ttf
Normal file
Binary file not shown.
3
app/static/semantic/fonts/OFL.txt
Executable file → Normal file
3
app/static/semantic/fonts/OFL.txt
Executable file → Normal file
@ -1,5 +1,4 @@
|
|||||||
Copyright (c) 2010-2015, Łukasz Dziedzic (dziedzic@typoland.com),
|
Copyright 2011 The Mulish Project Authors (github.com/googlefonts/mulish)
|
||||||
with Reserved Font Name Lato.
|
|
||||||
|
|
||||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
This license is copied below, and is also available with a FAQ at:
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
2
app/static/semantic/semantic.min.css
vendored
2
app/static/semantic/semantic.min.css
vendored
@ -10,7 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Lato';
|
font-family: 'Lato';
|
||||||
src: url('fonts/Lato-Regular.ttf'); /* IE9 Compat Modes */
|
src: url('fonts/Mulish-Regular.ttf'); /* IE9 Compat Modes */
|
||||||
}/*!
|
}/*!
|
||||||
* # Semantic UI 2.4.0 - Reset
|
* # Semantic UI 2.4.0 - Reset
|
||||||
* http://github.com/semantic-org/semantic-ui/
|
* http://github.com/semantic-org/semantic-ui/
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<img src="{{video.videoThumb}}">
|
<img src="{{video.videoThumb}}">
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<a class="video-title" target="_blank" href="{{video.videoUrl}}">{{video.videoTitle}}</a>
|
<a class="video-title" href="{{url_for('video', id=video.id)}}">{{video.videoTitle}}</a>
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
<a href="{{video.channelUrl}}">{{video.channelName}}</a>
|
<a href="{{video.channelUrl}}">{{video.channelName}}</a>
|
||||||
</div>
|
</div>
|
@ -1,27 +1,12 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<br>
|
<br>
|
||||||
<div class="ui one column centered grid">
|
<br>
|
||||||
<form class="ui form" action="" method="post" novalidate>
|
|
||||||
{{ form.hidden_tag() }}
|
|
||||||
<p>
|
|
||||||
{{ form.channelId.label }}<br>
|
|
||||||
{{ form.channelId(size=32) }}<br>
|
|
||||||
{% for error in form.channelId.errors %}
|
|
||||||
<span style="color: red;">[{{ error }}]</span>
|
|
||||||
{% endfor %}
|
|
||||||
</p>
|
|
||||||
<p>{{ form.submit() }}</p>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
|
|
||||||
{% if videos %}
|
{% if videos %}
|
||||||
<div class="ui centered cards">
|
<div class="ui centered cards">
|
||||||
{% for video in videos %}
|
{% for video in videos %}
|
||||||
{% include '_video.html' %}
|
{% include '_video_item.html' %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
43
app/templates/video.html
Normal file
43
app/templates/video.html
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div style="margin-top: 2em;" class="ui one column centered grid">
|
||||||
|
<iframe id='ivplayer' width='640' height='360' src='https://{{video.instance}}/embed/{{video.id}}' style='border:none;'>
|
||||||
|
</iframe>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top: 2em;" class="ui one column center aligned grid">
|
||||||
|
<a href="https://{{video.instance}}/watch?v={{video.id}}"><h2 class="ui header">{{video.title}}</h2></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top: 2em;" class="ui one column center aligned grid">
|
||||||
|
<a target="_blank" href="https://{{video.instance}}{{video.authorUrl}}" class="ui image label">
|
||||||
|
<img src="{{video.authorThumb}}">
|
||||||
|
{%if video.author.__len__() > 8%}
|
||||||
|
{{video.author[0:8]+'...'}}
|
||||||
|
{%else%}
|
||||||
|
{{video.author}}
|
||||||
|
{%endif%}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="ui label">
|
||||||
|
<i class="eye icon"></i> {{video.viewCount}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ui label">
|
||||||
|
<i class="thumbs up green icon"></i> {{video.likeCount}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ui label">
|
||||||
|
<i class="thumbs down red icon"></i> {{video.dislikeCount}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top: 2em;" class="ui one column center aligned grid">
|
||||||
|
<div style="margin-bottom: 2em;" class="ui comments">
|
||||||
|
<h3 class="ui dividing header">Description</h3>
|
||||||
|
{{video.description}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -14,9 +14,19 @@
|
|||||||
<p>{{ form.submit() }}</p>
|
<p>{{ form.submit() }}</p>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<div class="ui one column middle aligned grid">
|
||||||
|
<div class="ui message">
|
||||||
|
<div class="header">
|
||||||
|
Tip: Videos are shown below channels.
|
||||||
|
</div>
|
||||||
|
<p>Just scroll down!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if results %}
|
{% if results %}
|
||||||
<div class="ui one column centered grid">
|
<div class="ui one column centered grid">
|
||||||
<div class="ui middle aligned divided list">
|
<div class="ui middle aligned divided list">
|
||||||
|
<h3 class="ui dividing header">Users</h3>
|
||||||
{% for res in results %}
|
{% for res in results %}
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<div class="right floated content">
|
<div class="right floated content">
|
||||||
@ -46,6 +56,20 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="ui middle aligned divided list">
|
||||||
|
<h3 class="ui dividing header">Videos</h3>
|
||||||
|
{% if videos %}
|
||||||
|
<div class="ui centered cards">
|
||||||
|
{% for video in videos %}
|
||||||
|
{% include '_video_item.html' %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{% include '_empty_feed.html' %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user