pyfedi/app/main/routes.py

351 lines
16 KiB
Python
Raw Normal View History

2024-02-10 06:41:24 +13:00
import os.path
2024-01-03 20:14:39 +13:00
from datetime import datetime, timedelta
from math import log
2024-01-19 15:08:39 +13:00
from random import randint
2024-01-03 20:14:39 +13:00
from sqlalchemy.sql.operators import or_
from app import db, cache
from app.activitypub.util import default_context, make_image_sizes_async, refresh_user_profile, find_actor_or_create
2024-01-11 20:52:09 +13:00
from app.constants import SUBSCRIPTION_PENDING, SUBSCRIPTION_MEMBER, POST_TYPE_IMAGE, POST_TYPE_LINK, \
SUBSCRIPTION_OWNER, SUBSCRIPTION_MODERATOR
2024-01-19 15:08:39 +13:00
from app.inoculation import inoculation
2023-07-28 16:22:12 +12:00
from app.main import bp
from flask import g, session, flash, request, current_app, url_for, redirect, make_response, jsonify
2023-07-28 16:22:12 +12:00
from flask_moment import moment
from flask_login import current_user
2023-07-28 16:22:12 +12:00
from flask_babel import _, get_locale
2024-01-07 13:35:36 +13:00
from sqlalchemy import select, desc, text
2023-09-05 20:25:02 +12:00
from sqlalchemy_searchable import search
from app.utils import render_template, get_setting, gibberish, request_etag_matches, return_304, blocked_domains, \
2024-01-12 12:34:08 +13:00
ap_datetime, ip_address, retrieve_block_list, shorten_string, markdown_to_text, user_filters_home, \
joined_communities, moderating_communities, parse_page, theme_list, get_request
from app.models import Community, CommunityMember, Post, Site, User, utcnow, Domain, Topic, File, Instance, InstanceRole
2024-01-13 18:18:32 +13:00
from PIL import Image
import pytesseract
2023-08-22 21:24:11 +12:00
2023-07-28 16:22:12 +12:00
@bp.route('/', methods=['HEAD', 'GET', 'POST'])
2024-01-12 17:15:08 +13:00
@bp.route('/home', methods=['GET', 'POST'])
@bp.route('/home/<sort>', methods=['GET', 'POST'])
def index(sort=None):
if 'application/ld+json' in request.headers.get('Accept', '') or 'application/activity+json' in request.headers.get(
'Accept', ''):
return activitypub_application()
2024-01-12 17:15:08 +13:00
return home_page('home', sort)
2023-08-22 21:24:11 +12:00
2024-01-12 17:15:08 +13:00
@bp.route('/popular', methods=['GET'])
@bp.route('/popular/<sort>', methods=['GET'])
def popular(sort=None):
2024-01-12 17:15:08 +13:00
return home_page('popular', sort)
2024-01-03 20:14:39 +13:00
2024-01-12 17:15:08 +13:00
@bp.route('/all', methods=['GET'])
@bp.route('/all/<sort>', methods=['GET'])
def all_posts(sort=None):
2024-01-12 17:15:08 +13:00
return home_page('all', sort)
2024-01-03 20:14:39 +13:00
def home_page(type, sort):
2024-01-03 20:14:39 +13:00
verification_warning()
if sort is None:
sort = current_user.default_sort if current_user.is_authenticated else 'hot'
2024-01-03 20:14:39 +13:00
# If nothing has changed since their last visit, return HTTP 304
current_etag = f"{type}_{sort}_{hash(str(g.site.last_active))}"
2024-01-03 20:14:39 +13:00
if current_user.is_anonymous and request_etag_matches(current_etag):
return return_304(current_etag)
page = request.args.get('page', 1, type=int)
2024-02-13 21:28:33 +13:00
low_bandwidth = request.cookies.get('low_bandwidth', '0') == '1'
2024-01-03 20:14:39 +13:00
if current_user.is_anonymous:
2024-01-12 17:15:08 +13:00
flash(_('Create an account to tailor this feed to your interests.'))
2024-01-03 20:14:39 +13:00
posts = Post.query.filter(Post.from_bot == False, Post.nsfw == False, Post.nsfl == False)
2024-01-12 17:15:08 +13:00
posts = posts.join(Community, Community.id == Post.community_id)
if type == 'home':
posts = posts.filter(Community.show_home == True)
elif type == 'popular':
posts = posts.filter(Community.show_popular == True).filter(Post.score > 100)
elif type == 'all':
posts = posts.filter(Community.show_all == True)
2024-01-11 20:39:22 +13:00
content_filters = {}
2024-01-03 20:14:39 +13:00
else:
2024-01-12 17:15:08 +13:00
if type == 'home':
posts = Post.query.join(CommunityMember, Post.community_id == CommunityMember.community_id).filter(
CommunityMember.is_banned == False)
# posts = posts.join(User, CommunityMember.user_id == User.id).filter(User.id == current_user.id)
posts = posts.filter(CommunityMember.user_id == current_user.id)
elif type == 'popular':
posts = Post.query.filter(Post.from_bot == False)
posts = posts.join(Community, Community.id == Post.community_id)
posts = posts.filter(Community.show_popular == True, Post.score > 100)
elif type == 'all':
posts = Post.query
posts = posts.join(Community, Community.id == Post.community_id)
posts = posts.filter(Community.show_all == True)
if current_user.ignore_bots:
posts = posts.filter(Post.from_bot == False)
if current_user.show_nsfl is False:
posts = posts.filter(Post.nsfl == False)
if current_user.show_nsfw is False:
posts = posts.filter(Post.nsfw == False)
2024-01-03 20:14:39 +13:00
domains_ids = blocked_domains(current_user.id)
if domains_ids:
posts = posts.filter(or_(Post.domain_id.not_in(domains_ids), Post.domain_id == None))
2024-01-11 20:39:22 +13:00
content_filters = user_filters_home(current_user.id)
2024-01-03 20:14:39 +13:00
2024-01-12 17:15:08 +13:00
# Sorting
if sort == 'hot':
posts = posts.order_by(desc(Post.ranking))
elif sort == 'top':
posts = posts.filter(Post.posted_at > utcnow() - timedelta(days=1)).order_by(desc(Post.score))
elif sort == 'new':
posts = posts.order_by(desc(Post.posted_at))
elif sort == 'active':
posts = posts.order_by(desc(Post.last_active))
2024-01-12 17:15:08 +13:00
# Pagination
2024-02-13 21:28:33 +13:00
posts = posts.paginate(page=page, per_page=100 if current_user.is_authenticated and not low_bandwidth else 50, error_out=False)
2024-01-12 17:15:08 +13:00
if type == 'home':
next_url = url_for('main.index', page=posts.next_num, sort=sort) if posts.has_next else None
prev_url = url_for('main.index', page=posts.prev_num, sort=sort) if posts.has_prev and page != 1 else None
elif type == 'popular':
next_url = url_for('main.popular', page=posts.next_num, sort=sort) if posts.has_next else None
prev_url = url_for('main.popular', page=posts.prev_num, sort=sort) if posts.has_prev and page != 1 else None
elif type == 'all':
next_url = url_for('main.all_posts', page=posts.next_num, sort=sort) if posts.has_next else None
prev_url = url_for('main.all_posts', page=posts.prev_num, sort=sort) if posts.has_prev and page != 1 else None
2024-01-03 20:14:39 +13:00
active_communities = Community.query.filter_by(banned=False).order_by(desc(Community.last_active)).limit(5).all()
2024-01-12 17:15:08 +13:00
return render_template('index.html', posts=posts, active_communities=active_communities, show_post_community=True,
POST_TYPE_IMAGE=POST_TYPE_IMAGE, POST_TYPE_LINK=POST_TYPE_LINK,
2024-02-13 21:28:33 +13:00
low_bandwidth=low_bandwidth,
2024-01-03 20:14:39 +13:00
SUBSCRIPTION_PENDING=SUBSCRIPTION_PENDING, SUBSCRIPTION_MEMBER=SUBSCRIPTION_MEMBER,
etag=f"{type}_{sort}_{hash(str(g.site.last_active))}", next_url=next_url, prev_url=prev_url,
2024-01-03 20:14:39 +13:00
rss_feed=f"https://{current_app.config['SERVER_NAME']}/feed",
2024-01-12 17:15:08 +13:00
rss_feed_name=f"Posts on " + g.site.name,
title=f"{g.site.name} - {g.site.description}",
description=shorten_string(markdown_to_text(g.site.sidebar), 150),
content_filters=content_filters, type=type, sort=sort,
moderating_communities=moderating_communities(current_user.get_id()),
2024-01-19 15:08:39 +13:00
joined_communities=joined_communities(current_user.get_id()),
inoculation=inoculation[randint(0, len(inoculation) - 1)])
2024-01-12 12:34:08 +13:00
@bp.route('/topics', methods=['GET'])
def list_topics():
verification_warning()
topics = Topic.query.order_by(Topic.name).all()
return render_template('list_topics.html', topics=topics, title=_('Browse by topic'),
2024-01-22 21:14:40 +13:00
low_bandwidth=request.cookies.get('low_bandwidth', '0') == '1',
moderating_communities=moderating_communities(current_user.get_id()),
2024-01-12 12:34:08 +13:00
joined_communities=joined_communities(current_user.get_id()))
2024-01-03 20:14:39 +13:00
2023-08-22 21:24:11 +12:00
@bp.route('/communities', methods=['GET'])
def list_communities():
2023-10-23 13:03:35 +13:00
verification_warning()
2023-09-05 20:25:02 +12:00
search_param = request.args.get('search', '')
topic_id = int(request.args.get('topic_id', 0))
sort_by = text('community.' + request.args.get('sort_by') if request.args.get('sort_by') else 'community.post_reply_count desc')
topics = Topic.query.order_by(Topic.name).all()
2024-02-02 15:57:22 +13:00
communities = Community.query.filter_by(banned=False)
2023-09-05 20:25:02 +12:00
if search_param == '':
pass
2023-09-05 20:25:02 +12:00
else:
2024-02-02 15:57:22 +13:00
communities = communities.filter(or_(Community.title.ilike(f"%{search_param}%"), Community.ap_id.ilike(f"%{search_param}%")))
#query = search(select(Community), search_param, sort=True) # todo: exclude banned communities from search
#communities = db.session.scalars(query).all()
2024-02-02 15:57:22 +13:00
if topic_id != 0:
communities = communities.filter_by(topic_id=topic_id)
2023-09-05 20:25:02 +12:00
2024-01-07 13:35:36 +13:00
return render_template('list_communities.html', communities=communities.order_by(sort_by).all(), search=search_param, title=_('Communities'),
SUBSCRIPTION_PENDING=SUBSCRIPTION_PENDING, SUBSCRIPTION_MEMBER=SUBSCRIPTION_MEMBER,
2024-01-11 20:52:09 +13:00
SUBSCRIPTION_OWNER=SUBSCRIPTION_OWNER, SUBSCRIPTION_MODERATOR=SUBSCRIPTION_MODERATOR,
topics=topics, topic_id=topic_id, sort_by=sort_by,
2024-01-12 12:34:08 +13:00
low_bandwidth=request.cookies.get('low_bandwidth', '0') == '1', moderating_communities=moderating_communities(current_user.get_id()),
joined_communities=joined_communities(current_user.get_id()))
2023-09-05 20:25:02 +12:00
@bp.route('/communities/local', methods=['GET'])
def list_local_communities():
2023-10-23 13:03:35 +13:00
verification_warning()
sort_by = text('community.' + request.args.get('sort_by') if request.args.get('sort_by') else 'community.post_reply_count desc')
communities = Community.query.filter_by(ap_id=None, banned=False)
return render_template('list_communities.html', communities=communities.order_by(sort_by).all(), title=_('Local communities'), sort_by=sort_by,
2024-01-08 19:41:32 +13:00
SUBSCRIPTION_PENDING=SUBSCRIPTION_PENDING, SUBSCRIPTION_MEMBER=SUBSCRIPTION_MEMBER,
2024-01-11 20:52:09 +13:00
SUBSCRIPTION_MODERATOR=SUBSCRIPTION_MODERATOR,
2024-01-12 12:34:08 +13:00
low_bandwidth=request.cookies.get('low_bandwidth', '0') == '1', moderating_communities=moderating_communities(current_user.get_id()),
joined_communities=joined_communities(current_user.get_id()))
2023-09-05 20:25:02 +12:00
@bp.route('/communities/subscribed', methods=['GET'])
def list_subscribed_communities():
2023-10-23 13:03:35 +13:00
verification_warning()
sort_by = text('community.' + request.args.get('sort_by') if request.args.get('sort_by') else 'community.post_reply_count desc')
if current_user.is_authenticated:
communities = Community.query.filter_by(banned=False).join(CommunityMember).filter(CommunityMember.user_id == current_user.id)
else:
communities = []
return render_template('list_communities.html', communities=communities.order_by(sort_by).all(), title=_('Joined communities'),
2024-01-08 19:41:32 +13:00
SUBSCRIPTION_PENDING=SUBSCRIPTION_PENDING, SUBSCRIPTION_MEMBER=SUBSCRIPTION_MEMBER, sort_by=sort_by,
2024-01-11 20:52:09 +13:00
SUBSCRIPTION_MODERATOR=SUBSCRIPTION_MODERATOR,
2024-01-12 12:34:08 +13:00
low_bandwidth=request.cookies.get('low_bandwidth', '0') == '1', moderating_communities=moderating_communities(current_user.get_id()),
joined_communities=joined_communities(current_user.get_id()))
2024-01-05 16:14:55 +13:00
@bp.route('/donate')
def donate():
return render_template('donate.html')
@bp.route('/privacy')
def privacy():
return render_template('privacy.html')
@bp.route('/login')
def login():
return redirect(url_for('auth.login'))
@bp.route('/robots.txt')
def robots():
resp = make_response(render_template('robots.txt'))
resp.mimetype = 'text/plain'
return resp
2024-01-24 17:02:48 +13:00
@bp.route('/sitemap.xml')
@cache.cached(timeout=6000)
def sitemap():
posts = Post.query.filter(Post.from_bot == False)
posts = posts.join(Community, Community.id == Post.community_id)
posts = posts.filter(Community.show_all == True, Community.ap_id == None) # sitemap.xml only includes local posts
if not g.site.enable_nsfw:
posts = posts.filter(Community.nsfw == False)
if not g.site.enable_nsfl:
posts = posts.filter(Community.nsfl == False)
posts = posts.order_by(desc(Post.posted_at))
resp = make_response(render_template('sitemap.xml', posts=posts, current_app=current_app))
resp.mimetype = 'text/xml'
return resp
2024-01-21 12:14:05 +13:00
@bp.route('/keyboard_shortcuts')
def keyboard_shortcuts():
return render_template('keyboard_shortcuts.html')
2024-02-10 12:20:18 +13:00
def list_files(directory):
for root, dirs, files in os.walk(directory):
for file in files:
yield os.path.join(root, file)
@bp.route('/test')
def test():
2024-02-10 12:20:18 +13:00
instance = Instance.query.get(3)
if instance.updated_at < utcnow() - timedelta(days=7):
try:
response = get_request(f'https://{instance.domain}/api/v3/site')
except:
response = None
if response and response.status_code == 200:
try:
instance_data = response.json()
except:
instance_data = None
finally:
response.close()
if instance_data:
if 'admins' in instance_data:
admin_profile_ids = []
for admin in instance_data['admins']:
admin_profile_ids.append(admin['person']['actor_id'].lower())
user = find_actor_or_create(admin['person']['actor_id'])
if user and not instance.user_is_admin(user.id):
new_instance_role = InstanceRole(instance_id=instance.id, user_id=user.id, role='admin')
db.session.add(new_instance_role)
db.session.commit()
# remove any InstanceRoles that are no longer part of instance-data['admins']
for instance_admin in InstanceRole.query.filter_by(instance_id=instance.id):
if instance_admin.user.profile_id() not in admin_profile_ids:
db.session.query(InstanceRole).filter(
InstanceRole.user_id == instance_admin.user.id,
InstanceRole.instance_id == instance.id,
InstanceRole.role == 'admin').delete()
db.session.commit()
return 'Ok'
2024-02-10 12:20:18 +13:00
return ''
2024-02-10 11:42:18 +13:00
retval = ''
for user in User.query.all():
filesize = user.filesize()
num_content = user.num_content()
if filesize > 0 and num_content > 0:
retval += f'{user.id},"{user.ap_id}",{filesize},{num_content}\n'
return retval
return ''
2024-02-10 06:41:24 +13:00
deleted = 0
for user in User.query.all():
if not user.is_local():
if user.cover_id:
file = user.cover
if file.file_path and file.thumbnail_path:
if os.path.exists(file.file_path):
os.unlink(file.file_path)
deleted += 1
file.file_path = ''
2024-02-10 06:46:52 +13:00
db.session.commit()
2024-02-10 06:41:24 +13:00
return str(deleted) + ' done'
2024-01-22 21:14:40 +13:00
return current_app.config['SERVER_NAME']
2024-01-03 20:14:39 +13:00
#ip = request.headers.get('X-Forwarded-For') or request.remote_addr
#if ',' in ip: # Remove all but first ip addresses
# ip = ip[:ip.index(',')].strip()
#return ip
2023-10-23 13:03:35 +13:00
def verification_warning():
if hasattr(current_user, 'verified') and current_user.verified is False:
flash(_('Please click the link in your email inbox to verify your account.'), 'warning')
@cache.cached(timeout=6)
def activitypub_application():
application_data = {
'@context': default_context(),
'type': 'Application',
'id': f"https://{current_app.config['SERVER_NAME']}/",
'name': g.site.name,
'summary': g.site.description,
'published': ap_datetime(g.site.created_at),
'updated': ap_datetime(g.site.updated),
'inbox': f"https://{current_app.config['SERVER_NAME']}/site_inbox",
'outbox': f"https://{current_app.config['SERVER_NAME']}/site_outbox",
}
resp = jsonify(application_data)
resp.content_type = 'application/activity+json'
return resp