2023-08-29 03:01:06 -07:00
|
|
|
# if commands in this file are not working (e.g. 'flask translate') make sure you set the FLASK_APP environment variable.
|
|
|
|
# e.g. export FLASK_APP=pyfedi.py
|
2023-11-26 02:21:04 -08:00
|
|
|
from datetime import datetime, timedelta
|
|
|
|
|
2024-02-22 19:52:17 -08:00
|
|
|
import flask
|
|
|
|
from flask import json, current_app
|
|
|
|
from flask_babel import _
|
|
|
|
from sqlalchemy import or_, desc
|
2023-08-29 03:01:06 -07:00
|
|
|
|
|
|
|
from app import db
|
2023-07-27 21:22:12 -07:00
|
|
|
import click
|
|
|
|
import os
|
|
|
|
|
2023-12-16 03:12:49 -08:00
|
|
|
from app.activitypub.signature import RsaKeys
|
2023-10-18 02:23:59 -07:00
|
|
|
from app.auth.util import random_token
|
2024-02-22 19:52:17 -08:00
|
|
|
from app.email import send_verification_email, send_email
|
2023-12-11 11:53:35 -08:00
|
|
|
from app.models import Settings, BannedInstances, Interest, Role, User, RolePermission, Domain, ActivityPubLog, \
|
2024-02-22 19:52:17 -08:00
|
|
|
utcnow, Site, Instance, File, Notification, Post, CommunityMember
|
|
|
|
from app.utils import file_get_contents, retrieve_block_list, blocked_domains
|
2023-09-02 21:30:20 -07:00
|
|
|
|
2023-07-27 21:22:12 -07:00
|
|
|
|
|
|
|
def register(app):
|
|
|
|
@app.cli.group()
|
|
|
|
def translate():
|
|
|
|
"""Translation and localization commands."""
|
|
|
|
pass
|
|
|
|
|
|
|
|
@translate.command()
|
|
|
|
@click.argument('lang')
|
|
|
|
def init(lang):
|
|
|
|
"""Initialize a new language."""
|
|
|
|
if os.system('pybabel extract -F babel.cfg -k _l -o messages.pot .'):
|
|
|
|
raise RuntimeError('extract command failed')
|
|
|
|
if os.system(
|
|
|
|
'pybabel init -i messages.pot -d app/translations -l ' + lang):
|
|
|
|
raise RuntimeError('init command failed')
|
|
|
|
os.remove('messages.pot')
|
|
|
|
|
|
|
|
@translate.command()
|
|
|
|
def update():
|
|
|
|
"""Update all languages."""
|
|
|
|
if os.system('pybabel extract -F babel.cfg -k _l -o messages.pot .'):
|
|
|
|
raise RuntimeError('extract command failed')
|
|
|
|
if os.system('pybabel update -i messages.pot -d app/translations'):
|
|
|
|
raise RuntimeError('update command failed')
|
|
|
|
os.remove('messages.pot')
|
|
|
|
|
|
|
|
@translate.command()
|
|
|
|
def compile():
|
|
|
|
"""Compile all languages."""
|
|
|
|
if os.system('pybabel compile -d app/translations'):
|
|
|
|
raise RuntimeError('compile command failed')
|
2023-08-29 03:01:06 -07:00
|
|
|
|
|
|
|
@app.cli.command("init-db")
|
|
|
|
def init_db():
|
|
|
|
with app.app_context():
|
|
|
|
db.drop_all()
|
|
|
|
db.configure_mappers()
|
|
|
|
db.create_all()
|
2023-12-16 03:12:49 -08:00
|
|
|
private_key, public_key = RsaKeys.generate_keypair()
|
|
|
|
db.session.add(Site(name="PieFed", description='', public_key=public_key, private_key=private_key))
|
2023-12-27 23:00:07 -08:00
|
|
|
db.session.add(Instance(domain=app.config['SERVER_NAME'], software='PieFed')) # Instance 1 is always the local instance
|
2023-10-18 02:23:59 -07:00
|
|
|
db.session.add(Settings(name='allow_nsfw', value=json.dumps(False)))
|
|
|
|
db.session.add(Settings(name='allow_nsfl', value=json.dumps(False)))
|
|
|
|
db.session.add(Settings(name='allow_dislike', value=json.dumps(True)))
|
|
|
|
db.session.add(Settings(name='allow_local_image_posts', value=json.dumps(True)))
|
|
|
|
db.session.add(Settings(name='allow_remote_image_posts', value=json.dumps(True)))
|
|
|
|
db.session.add(Settings(name='registration_open', value=json.dumps(True)))
|
|
|
|
db.session.add(Settings(name='approve_registrations', value=json.dumps(False)))
|
|
|
|
db.session.add(Settings(name='federation', value=json.dumps(True)))
|
|
|
|
db.session.add(BannedInstances(domain='lemmygrad.ml'))
|
|
|
|
db.session.add(BannedInstances(domain='gab.com'))
|
|
|
|
db.session.add(BannedInstances(domain='rqd2.net'))
|
|
|
|
db.session.add(BannedInstances(domain='exploding-heads.com'))
|
|
|
|
db.session.add(BannedInstances(domain='hexbear.net'))
|
|
|
|
db.session.add(BannedInstances(domain='threads.net'))
|
2023-11-29 23:57:51 -08:00
|
|
|
db.session.add(BannedInstances(domain='pieville.net'))
|
2024-02-02 13:36:19 -08:00
|
|
|
db.session.add(BannedInstances(domain='noauthority.social'))
|
|
|
|
db.session.add(BannedInstances(domain='pieville.net'))
|
2024-02-08 15:52:16 -08:00
|
|
|
db.session.add(BannedInstances(domain='links.hackliberty.org'))
|
2023-09-05 01:25:10 -07:00
|
|
|
interests = file_get_contents('interests.txt')
|
2023-10-18 02:23:59 -07:00
|
|
|
db.session.add(Interest(name='🕊 Chilling', communities=parse_communities(interests, 'chilling')))
|
|
|
|
db.session.add(Interest(name='💭 Interesting stuff', communities=parse_communities(interests, 'interesting stuff')))
|
|
|
|
db.session.add(Interest(name='📰 News & Politics', communities=parse_communities(interests, 'news & politics')))
|
|
|
|
db.session.add(Interest(name='🎮 Gaming', communities=parse_communities(interests, 'gaming')))
|
|
|
|
db.session.add(Interest(name='🤓 Linux', communities=parse_communities(interests, 'linux')))
|
|
|
|
db.session.add(Interest(name='♻️ Environment', communities=parse_communities(interests, 'environment')))
|
|
|
|
db.session.add(Interest(name='🏳🌈 LGBTQ+', communities=parse_communities(interests, 'lgbtq')))
|
|
|
|
db.session.add(Interest(name='🛠 Programming', communities=parse_communities(interests, 'programming')))
|
|
|
|
db.session.add(Interest(name='🖥️ Tech', communities=parse_communities(interests, 'tech')))
|
|
|
|
db.session.add(Interest(name='🤗 Mental Health', communities=parse_communities(interests, 'mental health')))
|
2023-12-21 01:14:43 -08:00
|
|
|
db.session.add(Interest(name='💊 Health', communities=parse_communities(interests, 'health')))
|
2023-10-18 02:23:59 -07:00
|
|
|
|
2023-10-20 20:20:13 -07:00
|
|
|
# Load initial domain block list
|
|
|
|
block_list = retrieve_block_list()
|
|
|
|
if block_list:
|
2024-01-03 01:45:23 -08:00
|
|
|
for domain in block_list.split('\n'):
|
2023-10-20 20:20:13 -07:00
|
|
|
db.session.add(Domain(name=domain.strip(), banned=True))
|
|
|
|
|
2023-10-18 02:23:59 -07:00
|
|
|
# Initial roles
|
|
|
|
anon_role = Role(name='Anonymous user', weight=0)
|
|
|
|
anon_role.permissions.append(RolePermission(permission='register'))
|
|
|
|
db.session.add(anon_role)
|
|
|
|
|
|
|
|
auth_role = Role(name='Authenticated user', weight=1)
|
|
|
|
db.session.add(auth_role)
|
|
|
|
|
|
|
|
staff_role = Role(name='Staff', weight=2)
|
|
|
|
staff_role.permissions.append(RolePermission(permission='approve registrations'))
|
2023-10-20 19:49:01 -07:00
|
|
|
staff_role.permissions.append(RolePermission(permission='ban users'))
|
2024-01-02 19:29:58 -08:00
|
|
|
staff_role.permissions.append(RolePermission(permission='administer all communities'))
|
|
|
|
staff_role.permissions.append(RolePermission(permission='administer all users'))
|
2023-10-18 02:23:59 -07:00
|
|
|
db.session.add(staff_role)
|
|
|
|
|
|
|
|
admin_role = Role(name='Admin', weight=3)
|
2023-10-20 19:49:01 -07:00
|
|
|
admin_role.permissions.append(RolePermission(permission='approve registrations'))
|
2023-10-18 02:23:59 -07:00
|
|
|
admin_role.permissions.append(RolePermission(permission='change user roles'))
|
2023-10-20 19:49:01 -07:00
|
|
|
admin_role.permissions.append(RolePermission(permission='ban users'))
|
2023-10-18 02:23:59 -07:00
|
|
|
admin_role.permissions.append(RolePermission(permission='manage users'))
|
2023-11-03 01:59:48 -07:00
|
|
|
admin_role.permissions.append(RolePermission(permission='change instance settings'))
|
2023-12-30 15:09:20 -08:00
|
|
|
admin_role.permissions.append(RolePermission(permission='administer all communities'))
|
2024-01-02 19:29:58 -08:00
|
|
|
admin_role.permissions.append(RolePermission(permission='administer all users'))
|
2023-10-18 02:23:59 -07:00
|
|
|
db.session.add(admin_role)
|
|
|
|
|
|
|
|
# Admin user
|
|
|
|
user_name = input("Admin user name (ideally not 'admin'): ")
|
|
|
|
email = input("Admin email address: ")
|
|
|
|
password = input("Admin password: ")
|
|
|
|
verification_token = random_token(16)
|
|
|
|
admin_user = User(user_name=user_name, email=email, verification_token=verification_token)
|
|
|
|
admin_user.set_password(password)
|
|
|
|
admin_user.roles.append(admin_role)
|
2024-02-05 20:45:08 -08:00
|
|
|
admin_user.verified = True
|
2024-02-06 12:20:22 -08:00
|
|
|
db.session.add(admin_user)
|
2023-10-18 02:23:59 -07:00
|
|
|
|
2023-08-29 03:01:06 -07:00
|
|
|
db.session.commit()
|
2023-10-18 02:23:59 -07:00
|
|
|
print("Initial setup is finished.")
|
2023-09-05 01:25:10 -07:00
|
|
|
|
2023-11-26 02:21:04 -08:00
|
|
|
@app.cli.command('daily-maintenance')
|
|
|
|
def daily_maintenance():
|
|
|
|
with app.app_context():
|
|
|
|
"""Remove activity older than 3 days"""
|
|
|
|
db.session.query(ActivityPubLog).filter(
|
2023-12-11 11:53:35 -08:00
|
|
|
ActivityPubLog.created_at < utcnow() - timedelta(days=3)).delete()
|
2023-11-26 02:21:04 -08:00
|
|
|
db.session.commit()
|
|
|
|
|
2024-02-09 14:46:22 -08:00
|
|
|
@app.cli.command("spaceusage")
|
|
|
|
def spaceusage():
|
|
|
|
with app.app_context():
|
|
|
|
for user in User.query.all():
|
|
|
|
filesize = user.filesize()
|
|
|
|
num_content = user.num_content()
|
|
|
|
if filesize > 0 and num_content > 0:
|
|
|
|
print(f'{user.id},"{user.ap_id}",{filesize},{num_content}')
|
|
|
|
|
2024-02-09 15:20:18 -08:00
|
|
|
def list_files(directory):
|
|
|
|
for root, dirs, files in os.walk(directory):
|
|
|
|
for file in files:
|
|
|
|
yield os.path.join(root, file)
|
|
|
|
|
2024-02-09 22:58:34 -08:00
|
|
|
@app.cli.command("remove_orphan_files")
|
|
|
|
def remove_orphan_files():
|
|
|
|
""" Any user-uploaded file that does not have a corresponding entry in the File table should be deleted """
|
2024-02-09 15:20:18 -08:00
|
|
|
with app.app_context():
|
|
|
|
for file_path in list_files('app/static/media/users'):
|
|
|
|
if 'thumbnail' in file_path:
|
|
|
|
f = File.query.filter(File.thumbnail_path == file_path).first()
|
|
|
|
else:
|
|
|
|
f = File.query.filter(File.file_path == file_path).first()
|
|
|
|
if f is None:
|
2024-02-09 15:22:16 -08:00
|
|
|
os.unlink(file_path)
|
2023-09-05 01:25:10 -07:00
|
|
|
|
2024-02-22 19:52:17 -08:00
|
|
|
@app.cli.command("send_missed_notifs")
|
|
|
|
def send_missed_notifs():
|
|
|
|
with app.app_context():
|
|
|
|
users_to_notify = User.query.join(Notification, User.id == Notification.user_id).filter(
|
|
|
|
User.ap_id == None,
|
|
|
|
Notification.created_at > User.last_seen,
|
|
|
|
Notification.read == False,
|
|
|
|
User.email_unread_sent == False, # they have not been emailed since last activity
|
|
|
|
User.email_unread == True # they want to be emailed
|
|
|
|
).all()
|
|
|
|
|
|
|
|
for user in users_to_notify:
|
|
|
|
notifications = Notification.query.filter(Notification.user_id == user.id, Notification.read == False,
|
|
|
|
Notification.created_at > user.last_seen).all()
|
|
|
|
if notifications:
|
|
|
|
# Also get the top 20 posts since their last login
|
|
|
|
posts = Post.query.join(CommunityMember, Post.community_id == CommunityMember.community_id).filter(
|
|
|
|
CommunityMember.is_banned == False)
|
|
|
|
posts = posts.filter(CommunityMember.user_id == user.id)
|
|
|
|
if user.ignore_bots:
|
|
|
|
posts = posts.filter(Post.from_bot == False)
|
|
|
|
if user.show_nsfl is False:
|
|
|
|
posts = posts.filter(Post.nsfl == False)
|
|
|
|
if user.show_nsfw is False:
|
|
|
|
posts = posts.filter(Post.nsfw == False)
|
|
|
|
domains_ids = blocked_domains(user.id)
|
|
|
|
if domains_ids:
|
|
|
|
posts = posts.filter(or_(Post.domain_id.not_in(domains_ids), Post.domain_id == None))
|
|
|
|
posts = posts.filter(Post.posted_at > user.last_seen).order_by(desc(Post.score))
|
|
|
|
posts = posts.limit(20).all()
|
|
|
|
|
|
|
|
# Send email!
|
2024-02-23 16:33:28 -08:00
|
|
|
send_email(_('[PieFed] You have unread notifications'),
|
|
|
|
sender=f'PieFed <noreply@{current_app.config["SERVER_NAME"]}>',
|
2024-02-22 19:52:17 -08:00
|
|
|
recipients=[user.email],
|
|
|
|
text_body=flask.render_template('email/unread_notifications.txt', user=user,
|
|
|
|
notifications=notifications),
|
|
|
|
html_body=flask.render_template('email/unread_notifications.html', user=user,
|
|
|
|
notifications=notifications,
|
|
|
|
posts=posts,
|
|
|
|
domain=current_app.config['SERVER_NAME']))
|
|
|
|
user.email_unread_sent = True
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
|
2023-09-05 01:25:10 -07:00
|
|
|
def parse_communities(interests_source, segment):
|
|
|
|
lines = interests_source.split("\n")
|
|
|
|
include_in_output = False
|
|
|
|
output = []
|
|
|
|
|
|
|
|
for line in lines:
|
|
|
|
line = line.strip()
|
|
|
|
if line == segment:
|
|
|
|
include_in_output = True
|
|
|
|
continue
|
|
|
|
elif line == '':
|
|
|
|
include_in_output = False
|
|
|
|
if include_in_output:
|
|
|
|
output.append(line)
|
|
|
|
|
|
|
|
return "\n".join(output)
|