From 41e78f36f8e59354e04821fdd2ff6ad432ad4059 Mon Sep 17 00:00:00 2001 From: pluja Date: Mon, 24 Aug 2020 12:34:17 +0200 Subject: [PATCH] Twitter follow logic and DB rewrite + Youtube logic improvements --- app/models.py | 45 +++++++- app/routes.py | 202 +++++++++++++++++++-------------- app/templates/following.html | 2 +- app/templates/settings.html | 11 ++ app/templates/user.html | 2 +- app/templates/youtube.html | 30 +++-- app/templates/ytfollowing.html | 36 ++++++ config.py | 2 +- 8 files changed, 225 insertions(+), 105 deletions(-) create mode 100644 app/templates/ytfollowing.html diff --git a/app/models.py b/app/models.py index 62d9cbc..e765b9d 100644 --- a/app/models.py +++ b/app/models.py @@ -13,6 +13,11 @@ channel_association = db.Table('channel_association', db.Column('user_id', db.Integer, db.ForeignKey('user.id')) ) # Association: CHANNEL --followed by--> [USERS] +twitter_association = db.Table('twitter_association', + db.Column('account_id', db.String, db.ForeignKey('twitterAccount.id')), + db.Column('user_id', db.Integer, db.ForeignKey('user.id')) +) # Association: ACCOUNT --followed by--> [USERS] + class User(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), index=True, unique=True) @@ -47,13 +52,28 @@ class User(UserMixin, db.Model): def saved_posts(self): return Post.query.filter_by(user_id=self.id) + + # TWITTER + def twitter_following_list(self): + return self.twitterFollowed.all() + + def is_following_tw(self, uname): + temp_cid = twitterFollow.query.filter_by(username = uname).first() + if temp_cid is None: + return False + else: + following = self.twitter_following_list() + for f in following: + if f.username == uname: + return True + return False # YOUTUBE def youtube_following_list(self): return self.youtubeFollowed.all() def is_following_yt(self, cid): - temp_cid = invidiousFollow.query.filter_by(channelId = cid).first() + temp_cid = youtubeFollow.query.filter_by(channelId = cid).first() if temp_cid is None: return False else: @@ -69,11 +89,16 @@ class User(UserMixin, db.Model): secondaryjoin=(followers.c.followed_id == id), backref=db.backref('followers', lazy='dynamic'), lazy='dynamic') - youtubeFollowed = db.relationship("invidiousFollow", + youtubeFollowed = db.relationship("youtubeFollow", secondary=channel_association, back_populates="followers", lazy='dynamic') + twitterFollowed = db.relationship("twitterFollow", + secondary=twitter_association, + back_populates="followers", + lazy='dynamic') + @login.user_loader def load_user(id): @@ -106,16 +131,28 @@ class ytPost(): id = 'isod' -class invidiousFollow(db.Model): +class youtubeFollow(db.Model): __tablename__ = 'channel' id = db.Column(db.Integer, primary_key=True) channelId = db.Column(db.String(30), nullable=False, unique=True) + channelName = db.Column(db.String(30)) followers = db.relationship('User', secondary=channel_association, back_populates="youtubeFollowed") def __repr__(self): - return ''.format(self.channelId) + return ''.format(self.channelName) + +class twitterFollow(db.Model): + __tablename__ = 'twitterAccount' + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(30), unique=True) + followers = db.relationship('User', + secondary=twitter_association, + back_populates="twitterFollowed") + + def __repr__(self): + return ''.format(self.username) class Post(db.Model): id = db.Column(db.Integer, primary_key=True) diff --git a/app/routes.py b/app/routes.py index 241996f..3e88b9b 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,10 +1,11 @@ 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.models import User, twitterPost, ytPost, Post, youtubeFollow, twitterFollow from flask_login import login_user, logout_user, current_user, login_required -from app.models import User, twitterPost, ytPost, Post, invidiousFollow from flask import Flask, Response, stream_with_context from requests_futures.sessions import FuturesSession from concurrent.futures import as_completed +from werkzeug.utils import secure_filename from youtube_search import YoutubeSearch from werkzeug.urls import url_parse from youtube_dl import YoutubeDL @@ -24,6 +25,11 @@ nitterInstanceII = "https://nitter.mastodont.cat/" ytChannelRss = "https://www.youtube.com/feeds/videos.xml?channel_id=" invidiousInstance = "invidio.us" +########################## +#### Global variables #### +########################## +ALLOWED_EXTENSIONS = {'json'} + ######################### #### Twitter Logic ###### ######################### @@ -32,19 +38,19 @@ invidiousInstance = "invidio.us" @login_required def index(): start_time = time.time() - following = current_user.following_list() - followed = current_user.followed.count() + followingList = current_user.twitter_following_list() + followCount = len(followingList) posts = [] avatarPath = "img/avatars/1.png" form = EmptyForm() - posts.extend(getFeed(following)) + posts.extend(getFeed(followingList)) posts.sort(key=lambda x: x.timeStamp, reverse=True) if not posts: profilePic = avatarPath else: profilePic = posts[0].userProfilePic 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=followCount, form=form) @app.route('/savePost/', methods=['POST']) @login_required @@ -86,71 +92,56 @@ def deleteSaved(id): def follow(username): form = EmptyForm() if form.validate_on_submit(): - user = User.query.filter_by(username=username).first() - isTwitter = isTwitterUser(username) - if user is None and isTwitter: - x = ''.join(randomrandom.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(16)) - newUser = User(username=username, email="{}@person.is".format(x)) - db.session.add(newUser) + if twFollow(username): + flash("{} followed!".format(username)) + else: + flash("Something went wrong...") + return redirect(request.referrer) + +def twFollow(username): + if isTwitterUser(username): + try: + follow = twitterFollow() + follow.username = username + follow.followers.append(current_user) + db.session.add(follow) db.session.commit() - flash('You are now following {}!'.format(username)) - #flash('User {} not found.'.format(username)) - return redirect(url_for('index')) - if user == current_user: - flash('You cannot follow yourself!') - return redirect(url_for('user', username=username)) - current_user.follow(user) - db.session.commit() - flash('You are following {}!'.format(username)) - return redirect(url_for('user', username=username)) + return True + except: + flash("Couldn't follow {}. Maybe you are already following!".format(username)) + return False else: - return redirect(url_for('index')) + flash("Something went wrong... try again") + return False @app.route('/unfollow/', methods=['POST']) @login_required def unfollow(username): form = EmptyForm() if form.validate_on_submit(): - user = User.query.filter_by(username=username).first() - if user is None: - flash('User {} not found.'.format(username)) - return redirect(url_for('index')) - if user == current_user: - flash('You cannot unfollow yourself!') - return redirect(url_for('user', username=username)) - current_user.unfollow(user) - db.session.commit() - flash('You are no longer following {}.'.format(username)) - return redirect(url_for('user', username=username)) - else: - return redirect(url_for('index')) + if twUnfollow(username): + flash("{} unfollowed!".format(username)) + else: + flash("Something went wrong...") + return redirect(request.referrer) -@app.route('/unfollowList/', methods=['POST']) -@login_required -def unfollowList(username): - form = EmptyForm() - if form.validate_on_submit(): - user = User.query.filter_by(username=username).first() - if user is None: - flash('User {} not found.'.format(username)) - return redirect(url_for('index')) - if user == current_user: - flash('You cannot unfollow yourself!') - return redirect(url_for('user', username=username)) - current_user.unfollow(user) +def twUnfollow(username): + try: + user = twitterFollow.query.filter_by(username=username).first() + db.session.delete(user) db.session.commit() - flash('You are no longer following {}!'.format(username)) - return redirect(url_for('following')) - else: - return redirect(url_for('index')) + flash("{} unfollowed!".format(username)) + except: + flash("There was an error unfollowing the user. Try again.") + return redirect(request.referrer) @app.route('/following') @login_required def following(): form = EmptyForm() - following = current_user.following_list() - followed = current_user.followed.count() - return render_template('following.html', accounts = following, count = followed, form = form) + followCount = len(current_user.twitter_following_list()) + accounts = current_user.twitter_following_list() + return render_template('following.html', accounts = accounts, count = followCount, form = form) @app.route('/search', methods=['GET', 'POST']) @login_required @@ -174,16 +165,9 @@ def search(): @app.route('/user/') @login_required def user(username): - user = User.query.filter_by(username=username).first() - isTwitter = isTwitterUser(username) - - if isTwitter and user is None: - x = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(16)) - newUser = User(username=username, email="{}@person.is".format(x)) - db.session.add(newUser) - db.session.commit() - - elif not isTwitter and user is None: + isTwitter = isTwitterUser(username) + if not isTwitter: + flash("This user is not on Twitter.") return redirect( url_for('error', errno="404")) posts = [] @@ -202,13 +186,23 @@ def user(username): @app.route('/youtube', methods=['GET', 'POST']) @login_required def youtube(): + followCount = len(current_user.youtube_following_list()) start_time = time.time() ids = current_user.youtube_following_list() videos = getYoutubePosts(ids) if videos: videos.sort(key=lambda x: x.date, reverse=True) print("--- {} seconds fetching youtube feed---".format(time.time() - start_time)) - return render_template('youtube.html', videos=videos) + return render_template('youtube.html', videos=videos, followCount=followCount) + +@app.route('/ytfollowing', methods=['GET', 'POST']) +@login_required +def ytfollowing(): + form = EmptyForm() + channelList = current_user.youtube_following_list() + channelCount = len(channelList) + + return render_template('ytfollowing.html', form=form, channelList=channelList, channelCount=channelCount) @app.route('/ytsearch', methods=['GET', 'POST']) @login_required @@ -254,35 +248,41 @@ def ytsearch(): 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(request.referrer) - flash('You are following {}!'.format(channelId)) - else: - flash("Something went wrong... try again") - return redirect(request.referrer) - else: - return redirect(request.referrer) + r = followYoutubeChannel(channelId) + return redirect(request.referrer) + +def followYoutubeChannel(channelId): + channel = youtubeFollow.query.filter_by(channelId=channelId).first() + channelData = YoutubeSearch.channelInfo(channelId, False) + try: + follow = youtubeFollow() + follow.channelId = channelId + follow.channelName = channelData[0]['name'] + follow.followers.append(current_user) + db.session.add(follow) + db.session.commit() + flash("{} followed!".format(channelData[0]['name'])) + return True + except: + flash("Couldn't follow {}. Maybe you are already following!".format(channelData[0]['name'])) + return False @app.route('/ytunfollow/', methods=['POST']) @login_required def ytunfollow(channelId): form = EmptyForm() - channel = invidiousFollow.query.filter_by(channelId=channelId).first() + if form.validate_on_submit(): + r = unfollowYoutubeChannel(channelId) + return redirect(request.referrer) + +def unfollowYoutubeChannel(channelId): try: + channel = youtubeFollow.query.filter_by(channelId=channelId).first() db.session.delete(channel) db.session.commit() - flash("User unfollowed!") + flash("{} unfollowed!".format(channel.channelName)) except: + print("Exception") flash("There was an error unfollowing the user. Try again.") return redirect(request.referrer) @@ -295,7 +295,7 @@ def channel(id): data = feedparser.parse(data.content) channelData = YoutubeSearch.channelInfo(id) - return render_template('channel.html', form=form, btform=button_form, channel=channelData[0], videos=channelData[1]) + return render_template('channel.html', form=form, btform=button_form, channel=channelData[1], videos=channelData[0]) @app.route('/watch', methods=['GET']) @login_required @@ -397,6 +397,34 @@ def exportData(): except: return False +@app.route('/importdata', methods=['GET', 'POST']) +@login_required +def importdata(): + if request.method == 'POST': + print("Post request recieved") + # check if the post request has the file part + if 'file' not in request.files: + flash('No file part') + return redirect(request.url) + file = request.files['file'] + # if user does not select file, browser also + # submit an empty part without filename + if file.filename == '': + flash('No selected file') + return redirect(request.url) + if file and allowed_file(file.filename): + filename = secure_filename(file.filename) + data = json.load(file) + for acc in data['twitter']: + if twFollow(acc['username']): + print("{} followed!".format(acc['username'])) + else: + print("Something went wrong!") + return redirect(request.referrer) + +def allowed_file(filename): + return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS + @app.route('/register', methods=['GET', 'POST']) def register(): if current_user.is_authenticated: diff --git a/app/templates/following.html b/app/templates/following.html index 68513b7..44d9957 100644 --- a/app/templates/following.html +++ b/app/templates/following.html @@ -17,7 +17,7 @@

-

+ {{ form.hidden_tag() }} {{ form.submit(value='Unfollow') }}
diff --git a/app/templates/settings.html b/app/templates/settings.html index 2caa132..4861c5a 100644 --- a/app/templates/settings.html +++ b/app/templates/settings.html @@ -23,6 +23,17 @@
Export data into JSON file
+
+ +
+
+ + +
+
Import data from JSON file
+
+
+
diff --git a/app/templates/user.html b/app/templates/user.html index e46b4a1..8178003 100644 --- a/app/templates/user.html +++ b/app/templates/user.html @@ -18,7 +18,7 @@
{% if user == current_user %}

This is your profile

- {% elif not current_user.is_following(user) %} + {% elif not current_user.is_following_tw(user.username) %}

{{ form.hidden_tag() }} diff --git a/app/templates/youtube.html b/app/templates/youtube.html index 43af0d1..93e89fd 100644 --- a/app/templates/youtube.html +++ b/app/templates/youtube.html @@ -1,15 +1,23 @@ {% extends "base.html" %} {% block content %} -
-
- {% if videos %} -
- {% for video in videos %} - {% include '_video_item.html' %} - {% endfor %} -
- {% else %} - {% include '_empty_feed.html' %} - {% endif %} + + {% if videos %} +
+ {% for video in videos %} + {% include '_video_item.html' %} + {% endfor %} +
+ {% else %} + {% include '_empty_feed.html' %} + {% endif %} {% endblock %} \ No newline at end of file diff --git a/app/templates/ytfollowing.html b/app/templates/ytfollowing.html new file mode 100644 index 0000000..858dafb --- /dev/null +++ b/app/templates/ytfollowing.html @@ -0,0 +1,36 @@ +{% extends "base.html" %} + +{% block content %} + + +
+
+ {% for channel in channelList %} +
+
+

+ + {{ form.hidden_tag() }} + {{ form.submit(value='Unfollow') }} +

+

+
+ + +
+ {% endfor %} +
+
+ +{% endblock %} \ No newline at end of file diff --git a/config.py b/config.py index a1f300c..e6ba47f 100644 --- a/config.py +++ b/config.py @@ -6,4 +6,4 @@ class Config(object): SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess' SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \ 'sqlite:///' + os.path.join(basedir, 'app.db') - SQLALCHEMY_TRACK_MODIFICATIONS = False + SQLALCHEMY_TRACK_MODIFICATIONS = False \ No newline at end of file