2024-01-01 11:38:24 +13:00
|
|
|
from flask import redirect, url_for, flash, request, make_response, session, Markup, current_app, abort, g, json
|
2024-01-03 16:29:58 +13:00
|
|
|
from flask_login import current_user, login_required
|
2023-08-29 22:01:06 +12:00
|
|
|
from flask_babel import _
|
2023-11-21 23:05:07 +13:00
|
|
|
from sqlalchemy import or_, desc
|
2023-09-17 21:19:51 +12:00
|
|
|
|
2023-12-03 22:41:15 +13:00
|
|
|
from app import db, constants, cache
|
2024-01-03 16:29:58 +13:00
|
|
|
from app.activitypub.signature import RsaKeys, post_request
|
2023-12-03 22:41:15 +13:00
|
|
|
from app.activitypub.util import default_context
|
2023-12-21 22:14:43 +13:00
|
|
|
from app.community.forms import SearchRemoteCommunity, AddLocalCommunity, CreatePostForm, ReportCommunityForm, \
|
|
|
|
DeleteCommunityForm
|
2023-11-30 06:36:08 +13:00
|
|
|
from app.community.util import search_for_community, community_url_exists, actor_to_community, \
|
2023-12-26 21:39:52 +13:00
|
|
|
opengraph_parse, url_to_thumbnail_file, save_post, save_icon_file, save_banner_file, send_to_remote_instance
|
2023-12-03 22:41:15 +13:00
|
|
|
from app.constants import SUBSCRIPTION_MEMBER, SUBSCRIPTION_OWNER, POST_TYPE_LINK, POST_TYPE_ARTICLE, POST_TYPE_IMAGE, \
|
|
|
|
SUBSCRIPTION_PENDING
|
2023-11-30 06:36:08 +13:00
|
|
|
from app.models import User, Community, CommunityMember, CommunityJoinRequest, CommunityBan, Post, \
|
2024-01-01 11:38:24 +13:00
|
|
|
File, PostVote, utcnow, Report, Notification, InstanceBlock, ActivityPubLog
|
2023-08-29 22:01:06 +12:00
|
|
|
from app.community import bp
|
2023-10-23 20:18:46 +13:00
|
|
|
from app.utils import get_setting, render_template, allowlist_html, markdown_to_html, validation_required, \
|
2023-12-10 15:10:09 +13:00
|
|
|
shorten_string, markdown_to_text, domain_from_url, validate_image, gibberish, community_membership, ap_datetime, \
|
2024-01-03 16:29:58 +13:00
|
|
|
request_etag_matches, return_304, instance_banned, can_create, can_upvote, can_downvote
|
2023-12-12 18:28:49 +13:00
|
|
|
from feedgen.feed import FeedGenerator
|
2024-01-03 20:14:39 +13:00
|
|
|
from datetime import timezone, timedelta
|
2023-08-29 22:01:06 +12:00
|
|
|
|
|
|
|
|
|
|
|
@bp.route('/add_local', methods=['GET', 'POST'])
|
2023-10-23 13:03:35 +13:00
|
|
|
@login_required
|
2023-08-29 22:01:06 +12:00
|
|
|
def add_local():
|
|
|
|
form = AddLocalCommunity()
|
2023-12-31 12:09:20 +13:00
|
|
|
if g.site.enable_nsfw is False:
|
2023-09-03 16:30:20 +12:00
|
|
|
form.nsfw.render_kw = {'disabled': True}
|
|
|
|
|
2023-09-05 20:25:02 +12:00
|
|
|
if form.validate_on_submit() and not community_url_exists(form.url.data):
|
|
|
|
# todo: more intense data validation
|
2023-09-17 21:19:51 +12:00
|
|
|
if form.url.data.strip().lower().startswith('/c/'):
|
2023-09-05 20:25:02 +12:00
|
|
|
form.url.data = form.url.data[3:]
|
2023-09-03 16:30:20 +12:00
|
|
|
private_key, public_key = RsaKeys.generate_keypair()
|
|
|
|
community = Community(title=form.community_name.data, name=form.url.data, description=form.description.data,
|
2023-09-08 20:04:01 +12:00
|
|
|
rules=form.rules.data, nsfw=form.nsfw.data, private_key=private_key,
|
2024-01-04 22:08:32 +13:00
|
|
|
public_key=public_key, description_html=markdown_to_html(form.description.data),
|
|
|
|
rules_html=markdown_to_html(form.rules.data),
|
2023-12-13 21:04:11 +13:00
|
|
|
ap_profile_id='https://' + current_app.config['SERVER_NAME'] + '/c/' + form.url.data,
|
2023-12-27 14:38:41 +13:00
|
|
|
ap_followers_url='https://' + current_app.config['SERVER_NAME'] + '/c/' + form.url.data + '/followers',
|
2023-12-21 22:14:43 +13:00
|
|
|
subscriptions_count=1, instance_id=1, low_quality='memes' in form.url.data)
|
2023-12-08 17:13:38 +13:00
|
|
|
icon_file = request.files['icon_file']
|
|
|
|
if icon_file and icon_file.filename != '':
|
|
|
|
file = save_icon_file(icon_file)
|
|
|
|
if file:
|
|
|
|
community.icon = file
|
|
|
|
banner_file = request.files['banner_file']
|
|
|
|
if banner_file and banner_file.filename != '':
|
|
|
|
file = save_banner_file(banner_file)
|
|
|
|
if file:
|
|
|
|
community.image = file
|
2023-09-03 16:30:20 +12:00
|
|
|
db.session.add(community)
|
|
|
|
db.session.commit()
|
|
|
|
membership = CommunityMember(user_id=current_user.id, community_id=community.id, is_moderator=True,
|
|
|
|
is_owner=True)
|
|
|
|
db.session.add(membership)
|
|
|
|
db.session.commit()
|
|
|
|
flash(_('Your new community has been created.'))
|
|
|
|
return redirect('/c/' + community.name)
|
|
|
|
|
|
|
|
return render_template('community/add_local.html', title=_('Create community'), form=form)
|
2023-08-29 22:01:06 +12:00
|
|
|
|
|
|
|
|
|
|
|
@bp.route('/add_remote', methods=['GET', 'POST'])
|
2023-10-23 13:03:35 +13:00
|
|
|
@login_required
|
2023-08-29 22:01:06 +12:00
|
|
|
def add_remote():
|
|
|
|
form = SearchRemoteCommunity()
|
|
|
|
new_community = None
|
|
|
|
if form.validate_on_submit():
|
|
|
|
address = form.address.data.strip()
|
|
|
|
if address.startswith('!') and '@' in address:
|
|
|
|
new_community = search_for_community(address)
|
|
|
|
elif address.startswith('@') and '@' in address[1:]:
|
|
|
|
# todo: the user is searching for a person instead
|
|
|
|
...
|
|
|
|
elif '@' in address:
|
|
|
|
new_community = search_for_community('!' + address)
|
|
|
|
else:
|
2023-09-08 20:04:01 +12:00
|
|
|
message = Markup(
|
|
|
|
'Type address in the format !community@server.name. Search on <a href="https://lemmyverse.net/communities">Lemmyverse.net</a> to find some.')
|
2023-08-29 22:01:06 +12:00
|
|
|
flash(message, 'error')
|
|
|
|
|
|
|
|
return render_template('community/add_remote.html',
|
|
|
|
title=_('Add remote community'), form=form, new_community=new_community,
|
2023-12-03 22:41:15 +13:00
|
|
|
subscribed=community_membership(current_user, new_community) >= SUBSCRIPTION_MEMBER)
|
2023-08-29 22:01:06 +12:00
|
|
|
|
|
|
|
|
|
|
|
# @bp.route('/c/<actor>', methods=['GET']) - defined in activitypub/routes.py, which calls this function for user requests. A bit weird.
|
|
|
|
def show_community(community: Community):
|
2023-12-10 15:10:09 +13:00
|
|
|
|
|
|
|
# If nothing has changed since their last visit, return HTTP 304
|
|
|
|
current_etag = f"{community.id}_{hash(community.last_active)}"
|
2023-12-17 00:12:49 +13:00
|
|
|
if current_user.is_anonymous and request_etag_matches(current_etag):
|
2023-12-10 15:10:09 +13:00
|
|
|
return return_304(current_etag)
|
|
|
|
|
2024-01-01 11:38:24 +13:00
|
|
|
if community.banned:
|
|
|
|
abort(404)
|
|
|
|
|
2023-12-15 17:35:11 +13:00
|
|
|
page = request.args.get('page', 1, type=int)
|
2024-01-03 20:14:39 +13:00
|
|
|
sort = request.args.get('sort', '')
|
2023-12-15 17:35:11 +13:00
|
|
|
|
2023-09-17 21:19:51 +12:00
|
|
|
mods = community.moderators()
|
2023-09-05 20:25:02 +12:00
|
|
|
|
2023-10-02 22:16:44 +13:00
|
|
|
is_moderator = current_user.is_authenticated and any(mod.user_id == current_user.id for mod in mods)
|
2023-10-15 21:13:32 +13:00
|
|
|
is_owner = current_user.is_authenticated and any(
|
|
|
|
mod.user_id == current_user.id and mod.is_owner == True for mod in mods)
|
2023-12-22 14:05:39 +13:00
|
|
|
is_admin = current_user.is_authenticated and current_user.is_admin()
|
2023-09-05 20:25:02 +12:00
|
|
|
|
|
|
|
if community.private_mods:
|
|
|
|
mod_list = []
|
|
|
|
else:
|
|
|
|
mod_user_ids = [mod.user_id for mod in mods]
|
|
|
|
mod_list = User.query.filter(User.id.in_(mod_user_ids)).all()
|
|
|
|
|
2023-10-16 21:38:36 +13:00
|
|
|
if current_user.is_anonymous or current_user.ignore_bots:
|
2024-01-03 20:14:39 +13:00
|
|
|
posts = community.posts.filter(Post.from_bot == False)
|
2023-10-07 21:32:19 +13:00
|
|
|
else:
|
2024-01-03 20:14:39 +13:00
|
|
|
posts = community.posts
|
|
|
|
if sort == '' or sort == 'hot':
|
|
|
|
posts = posts.order_by(desc(Post.ranking))
|
|
|
|
elif sort == 'top':
|
|
|
|
posts = posts.filter(Post.posted_at > utcnow() - timedelta(days=7)).order_by(desc(Post.score))
|
|
|
|
elif sort == 'new':
|
|
|
|
posts = posts.order_by(desc(Post.posted_at))
|
|
|
|
posts = posts.paginate(page=page, per_page=100, error_out=False)
|
2023-10-07 21:32:19 +13:00
|
|
|
|
2023-10-23 20:18:46 +13:00
|
|
|
description = shorten_string(community.description, 150) if community.description else None
|
|
|
|
og_image = community.image.source_url if community.image_id else None
|
|
|
|
|
2023-12-15 17:35:11 +13:00
|
|
|
next_url = url_for('activitypub.community_profile', actor=community.ap_id if community.ap_id is not None else community.name,
|
2024-01-03 20:14:39 +13:00
|
|
|
page=posts.next_num, sort=sort) if posts.has_next else None
|
2023-12-15 17:35:11 +13:00
|
|
|
prev_url = url_for('activitypub.community_profile', actor=community.ap_id if community.ap_id is not None else community.name,
|
2024-01-03 20:14:39 +13:00
|
|
|
page=posts.prev_num, sort=sort) if posts.has_prev and page != 1 else None
|
2023-12-15 17:35:11 +13:00
|
|
|
|
2023-09-05 20:25:02 +12:00
|
|
|
return render_template('community/community.html', community=community, title=community.title,
|
2023-12-22 14:05:39 +13:00
|
|
|
is_moderator=is_moderator, is_owner=is_owner, is_admin=is_admin, mods=mod_list, posts=posts, description=description,
|
2023-12-03 22:41:15 +13:00
|
|
|
og_image=og_image, POST_TYPE_IMAGE=POST_TYPE_IMAGE, POST_TYPE_LINK=POST_TYPE_LINK, SUBSCRIPTION_PENDING=SUBSCRIPTION_PENDING,
|
2023-12-12 18:28:49 +13:00
|
|
|
SUBSCRIPTION_MEMBER=SUBSCRIPTION_MEMBER, etag=f"{community.id}_{hash(community.last_active)}",
|
2023-12-15 17:35:11 +13:00
|
|
|
next_url=next_url, prev_url=prev_url,
|
2023-12-12 18:28:49 +13:00
|
|
|
rss_feed=f"https://{current_app.config['SERVER_NAME']}/community/{community.link()}/feed", rss_feed_name=f"{community.title} posts on PieFed")
|
|
|
|
|
|
|
|
|
|
|
|
# RSS feed of the community
|
|
|
|
@bp.route('/<actor>/feed', methods=['GET'])
|
|
|
|
@cache.cached(timeout=600)
|
|
|
|
def show_community_rss(actor):
|
|
|
|
actor = actor.strip()
|
|
|
|
if '@' in actor:
|
|
|
|
community: Community = Community.query.filter_by(ap_id=actor, banned=False).first()
|
|
|
|
else:
|
|
|
|
community: Community = Community.query.filter_by(name=actor, banned=False, ap_id=None).first()
|
|
|
|
if community is not None:
|
|
|
|
# If nothing has changed since their last visit, return HTTP 304
|
|
|
|
current_etag = f"{community.id}_{hash(community.last_active)}"
|
|
|
|
if request_etag_matches(current_etag):
|
|
|
|
return return_304(current_etag, 'application/rss+xml')
|
|
|
|
|
|
|
|
posts = community.posts.filter(Post.from_bot == False).order_by(desc(Post.created_at)).limit(100).all()
|
|
|
|
description = shorten_string(community.description, 150) if community.description else None
|
|
|
|
og_image = community.image.source_url if community.image_id else None
|
|
|
|
fg = FeedGenerator()
|
|
|
|
fg.id(f"https://{current_app.config['SERVER_NAME']}/c/{actor}")
|
|
|
|
fg.title(community.title)
|
|
|
|
fg.link(href=f"https://{current_app.config['SERVER_NAME']}/c/{actor}", rel='alternate')
|
|
|
|
if og_image:
|
|
|
|
fg.logo(og_image)
|
|
|
|
else:
|
|
|
|
fg.logo(f"https://{current_app.config['SERVER_NAME']}/static/images/apple-touch-icon.png")
|
|
|
|
if description:
|
|
|
|
fg.subtitle(description)
|
|
|
|
else:
|
|
|
|
fg.subtitle(' ')
|
|
|
|
fg.link(href=f"https://{current_app.config['SERVER_NAME']}/c/{actor}/feed", rel='self')
|
|
|
|
fg.language('en')
|
|
|
|
|
|
|
|
for post in posts:
|
|
|
|
fe = fg.add_entry()
|
|
|
|
fe.title(post.title)
|
|
|
|
fe.link(href=f"https://{current_app.config['SERVER_NAME']}/post/{post.id}")
|
|
|
|
fe.description(post.body_html)
|
|
|
|
fe.guid(post.profile_id(), permalink=True)
|
|
|
|
fe.author(name=post.author.user_name)
|
|
|
|
fe.pubDate(post.created_at.replace(tzinfo=timezone.utc))
|
|
|
|
|
|
|
|
response = make_response(fg.rss_str())
|
|
|
|
response.headers.set('Content-Type', 'application/rss+xml')
|
|
|
|
response.headers.add_header('ETag', f"{community.id}_{hash(community.last_active)}")
|
|
|
|
response.headers.add_header('Cache-Control', 'no-cache, max-age=600, must-revalidate')
|
|
|
|
return response
|
|
|
|
else:
|
|
|
|
abort(404)
|
2023-09-05 20:25:02 +12:00
|
|
|
|
|
|
|
|
|
|
|
@bp.route('/<actor>/subscribe', methods=['GET'])
|
2023-10-02 22:16:44 +13:00
|
|
|
@login_required
|
2023-10-23 13:03:35 +13:00
|
|
|
@validation_required
|
2023-09-05 20:25:02 +12:00
|
|
|
def subscribe(actor):
|
2023-09-08 20:04:01 +12:00
|
|
|
remote = False
|
2023-09-05 20:25:02 +12:00
|
|
|
actor = actor.strip()
|
|
|
|
if '@' in actor:
|
|
|
|
community = Community.query.filter_by(banned=False, ap_id=actor).first()
|
2023-09-08 20:04:01 +12:00
|
|
|
remote = True
|
2023-09-05 20:25:02 +12:00
|
|
|
else:
|
|
|
|
community = Community.query.filter_by(name=actor, banned=False, ap_id=None).first()
|
|
|
|
|
|
|
|
if community is not None:
|
2023-12-03 22:41:15 +13:00
|
|
|
if community_membership(current_user, community) != SUBSCRIPTION_MEMBER and community_membership(current_user, community) != SUBSCRIPTION_PENDING:
|
2023-09-08 20:04:01 +12:00
|
|
|
if remote:
|
|
|
|
# send ActivityPub message to remote community, asking to follow. Accept message will be sent to our shared inbox
|
|
|
|
join_request = CommunityJoinRequest(user_id=current_user.id, community_id=community.id)
|
|
|
|
db.session.add(join_request)
|
|
|
|
db.session.commit()
|
|
|
|
follow = {
|
|
|
|
"actor": f"https://{current_app.config['SERVER_NAME']}/u/{current_user.user_name}",
|
2023-11-17 22:02:44 +13:00
|
|
|
"to": [community.ap_profile_id],
|
|
|
|
"object": community.ap_profile_id,
|
2023-09-08 20:04:01 +12:00
|
|
|
"type": "Follow",
|
2023-09-17 21:19:51 +12:00
|
|
|
"id": f"https://{current_app.config['SERVER_NAME']}/activities/follow/{join_request.id}"
|
2023-09-08 20:04:01 +12:00
|
|
|
}
|
2023-12-22 15:34:45 +13:00
|
|
|
success = post_request(community.ap_inbox_url, follow, current_user.private_key,
|
2023-11-17 22:02:44 +13:00
|
|
|
current_user.profile_id() + '#main-key')
|
2023-12-22 15:34:45 +13:00
|
|
|
if success:
|
2024-01-05 14:09:46 +13:00
|
|
|
flash('Your request to join has been sent to ' + community.title)
|
2023-12-22 15:34:45 +13:00
|
|
|
else:
|
2024-01-05 14:09:46 +13:00
|
|
|
flash('There was a problem while trying to join.', 'error')
|
2023-10-15 21:13:32 +13:00
|
|
|
else: # for local communities, joining is instant
|
2023-09-08 20:04:01 +12:00
|
|
|
banned = CommunityBan.query.filter_by(user_id=current_user.id, community_id=community.id).first()
|
|
|
|
if banned:
|
|
|
|
flash('You cannot join this community')
|
|
|
|
member = CommunityMember(user_id=current_user.id, community_id=community.id)
|
|
|
|
db.session.add(member)
|
|
|
|
db.session.commit()
|
2024-01-05 14:09:46 +13:00
|
|
|
flash('You joined ' + community.title)
|
2023-09-05 20:25:02 +12:00
|
|
|
referrer = request.headers.get('Referer', None)
|
|
|
|
if referrer is not None:
|
|
|
|
return redirect(referrer)
|
|
|
|
else:
|
|
|
|
return redirect('/c/' + actor)
|
|
|
|
else:
|
|
|
|
abort(404)
|
|
|
|
|
|
|
|
|
|
|
|
@bp.route('/<actor>/unsubscribe', methods=['GET'])
|
2023-10-02 22:16:44 +13:00
|
|
|
@login_required
|
2023-09-05 20:25:02 +12:00
|
|
|
def unsubscribe(actor):
|
2023-09-17 21:19:51 +12:00
|
|
|
community = actor_to_community(actor)
|
2023-09-05 20:25:02 +12:00
|
|
|
|
|
|
|
if community is not None:
|
2023-12-03 22:41:15 +13:00
|
|
|
subscription = community_membership(current_user, community)
|
2023-09-05 20:25:02 +12:00
|
|
|
if subscription:
|
|
|
|
if subscription != SUBSCRIPTION_OWNER:
|
2023-12-03 22:41:15 +13:00
|
|
|
proceed = True
|
|
|
|
# Undo the Follow
|
|
|
|
if '@' in actor: # this is a remote community, so activitypub is needed
|
2024-01-01 11:38:24 +13:00
|
|
|
undo_id = f"https://{current_app.config['SERVER_NAME']}/activities/undo/" + gibberish(15)
|
2023-12-03 22:41:15 +13:00
|
|
|
follow = {
|
|
|
|
"actor": f"https://{current_app.config['SERVER_NAME']}/u/{current_user.user_name}",
|
|
|
|
"to": [community.ap_profile_id],
|
|
|
|
"object": community.ap_profile_id,
|
|
|
|
"type": "Follow",
|
|
|
|
"id": f"https://{current_app.config['SERVER_NAME']}/activities/follow/{gibberish(15)}"
|
|
|
|
}
|
|
|
|
undo = {
|
|
|
|
'actor': current_user.profile_id(),
|
|
|
|
'to': [community.ap_profile_id],
|
|
|
|
'type': 'Undo',
|
2024-01-01 11:38:24 +13:00
|
|
|
'id': undo_id,
|
2023-12-03 22:41:15 +13:00
|
|
|
'object': follow
|
|
|
|
}
|
2024-01-01 11:38:24 +13:00
|
|
|
activity = ActivityPubLog(direction='out', activity_id=undo_id, activity_type='Undo',
|
|
|
|
activity_json=json.dumps(undo), result='processing')
|
|
|
|
db.session.add(activity)
|
|
|
|
db.session.commit()
|
2023-12-22 15:34:45 +13:00
|
|
|
success = post_request(community.ap_inbox_url, undo, current_user.private_key,
|
2023-12-03 22:41:15 +13:00
|
|
|
current_user.profile_id() + '#main-key')
|
2024-01-01 11:38:24 +13:00
|
|
|
activity.result = 'success'
|
|
|
|
db.session.commit()
|
2023-12-22 15:34:45 +13:00
|
|
|
if not success:
|
2024-01-05 14:09:46 +13:00
|
|
|
flash('There was a problem while trying to join', 'error')
|
2023-12-22 15:34:45 +13:00
|
|
|
|
2023-12-03 22:41:15 +13:00
|
|
|
if proceed:
|
|
|
|
db.session.query(CommunityMember).filter_by(user_id=current_user.id, community_id=community.id).delete()
|
|
|
|
db.session.query(CommunityJoinRequest).filter_by(user_id=current_user.id, community_id=community.id).delete()
|
|
|
|
db.session.commit()
|
|
|
|
|
2024-01-05 14:09:46 +13:00
|
|
|
flash('You are left ' + community.title)
|
2024-01-04 17:07:02 +13:00
|
|
|
cache.delete_memoized(community_membership, current_user, community)
|
2023-12-03 22:41:15 +13:00
|
|
|
|
2023-09-05 20:25:02 +12:00
|
|
|
else:
|
|
|
|
# todo: community deletion
|
|
|
|
flash('You need to make someone else the owner before unsubscribing.', 'warning')
|
|
|
|
|
|
|
|
# send them back where they came from
|
|
|
|
referrer = request.headers.get('Referer', None)
|
|
|
|
if referrer is not None:
|
|
|
|
return redirect(referrer)
|
|
|
|
else:
|
|
|
|
return redirect('/c/' + actor)
|
|
|
|
else:
|
|
|
|
abort(404)
|
2023-09-17 21:19:51 +12:00
|
|
|
|
|
|
|
|
|
|
|
@bp.route('/<actor>/submit', methods=['GET', 'POST'])
|
2023-10-02 22:16:44 +13:00
|
|
|
@login_required
|
2023-10-23 13:03:35 +13:00
|
|
|
@validation_required
|
2023-09-17 21:19:51 +12:00
|
|
|
def add_post(actor):
|
|
|
|
community = actor_to_community(actor)
|
2023-11-30 20:57:51 +13:00
|
|
|
form = CreatePostForm()
|
2023-12-31 12:09:20 +13:00
|
|
|
if g.site.enable_nsfw is False:
|
2023-09-17 21:19:51 +12:00
|
|
|
form.nsfw.render_kw = {'disabled': True}
|
2023-12-31 12:09:20 +13:00
|
|
|
if g.site.enable_nsfl is False:
|
2023-09-17 21:19:51 +12:00
|
|
|
form.nsfl.render_kw = {'disabled': True}
|
2023-12-09 22:14:16 +13:00
|
|
|
if community.nsfw:
|
|
|
|
form.nsfw.data = True
|
|
|
|
form.nsfw.render_kw = {'disabled': True}
|
|
|
|
if community.nsfl:
|
|
|
|
form.nsfl.data = True
|
|
|
|
form.nsfw.render_kw = {'disabled': True}
|
2023-12-08 17:13:38 +13:00
|
|
|
images_disabled = 'disabled' if not get_setting('allow_local_image_posts', True) else '' # bug: this will disable posting of images to *remote* hosts too
|
2023-09-17 21:19:51 +12:00
|
|
|
|
|
|
|
form.communities.choices = [(c.id, c.display_name()) for c in current_user.communities()]
|
|
|
|
|
2024-01-02 19:41:00 +13:00
|
|
|
if not can_create(current_user, community):
|
|
|
|
abort(401)
|
|
|
|
|
2023-09-17 21:19:51 +12:00
|
|
|
if form.validate_on_submit():
|
2023-12-26 21:39:52 +13:00
|
|
|
community = Community.query.get_or_404(form.communities.data)
|
2024-01-02 19:41:00 +13:00
|
|
|
if not can_create(current_user, community):
|
|
|
|
abort(401)
|
2023-12-17 00:12:49 +13:00
|
|
|
post = Post(user_id=current_user.id, community_id=form.communities.data, instance_id=1)
|
2023-11-30 20:57:51 +13:00
|
|
|
save_post(form, post)
|
2023-10-02 22:16:44 +13:00
|
|
|
community.post_count += 1
|
2023-12-26 21:39:52 +13:00
|
|
|
community.last_active = g.site.last_active = utcnow()
|
2023-09-17 21:19:51 +12:00
|
|
|
db.session.commit()
|
2023-12-09 22:14:16 +13:00
|
|
|
post.ap_id = f"https://{current_app.config['SERVER_NAME']}/post/{post.id}"
|
|
|
|
db.session.commit()
|
2023-09-17 21:19:51 +12:00
|
|
|
|
2023-12-26 21:39:52 +13:00
|
|
|
page = {
|
|
|
|
'type': 'Page',
|
|
|
|
'id': post.ap_id,
|
|
|
|
'attributedTo': current_user.ap_profile_id,
|
|
|
|
'to': [
|
|
|
|
community.ap_profile_id,
|
|
|
|
'https://www.w3.org/ns/activitystreams#Public'
|
|
|
|
],
|
|
|
|
'name': post.title,
|
|
|
|
'cc': [],
|
|
|
|
'content': post.body_html,
|
|
|
|
'mediaType': 'text/html',
|
|
|
|
'source': {
|
|
|
|
'content': post.body,
|
|
|
|
'mediaType': 'text/markdown'
|
|
|
|
},
|
|
|
|
'attachment': [],
|
|
|
|
'commentsEnabled': post.comments_enabled,
|
|
|
|
'sensitive': post.nsfw,
|
|
|
|
'nsfl': post.nsfl,
|
|
|
|
'published': ap_datetime(utcnow()),
|
|
|
|
'audience': community.ap_profile_id
|
|
|
|
}
|
|
|
|
create = {
|
|
|
|
"id": f"https://{current_app.config['SERVER_NAME']}/activities/create/{gibberish(15)}",
|
|
|
|
"actor": current_user.ap_profile_id,
|
|
|
|
"to": [
|
|
|
|
"https://www.w3.org/ns/activitystreams#Public"
|
|
|
|
],
|
|
|
|
"cc": [
|
|
|
|
community.ap_profile_id
|
|
|
|
],
|
|
|
|
"type": "Create",
|
|
|
|
"audience": community.ap_profile_id,
|
|
|
|
"object": page,
|
|
|
|
'@context': default_context()
|
|
|
|
}
|
2024-01-02 16:07:41 +13:00
|
|
|
if post.type == POST_TYPE_LINK:
|
|
|
|
page.attachment = [{'href': post.url, 'type': 'Link'}]
|
|
|
|
if post.image_id:
|
|
|
|
page.image = [{'type': 'Image', 'url': post.image.source_url}]
|
2023-12-09 22:14:16 +13:00
|
|
|
if not community.is_local(): # this is a remote community - send the post to the instance that hosts it
|
2023-12-26 21:39:52 +13:00
|
|
|
success = post_request(community.ap_inbox_url, create, current_user.private_key,
|
|
|
|
current_user.ap_profile_id + '#main-key')
|
|
|
|
if success:
|
|
|
|
flash(_('Your post to %(name)s has been made.', name=community.title))
|
|
|
|
else:
|
|
|
|
flash('There was a problem making your post to ' + community.title)
|
2024-01-03 16:29:58 +13:00
|
|
|
else: # local community - send (announce) post out to followers
|
2023-12-26 21:39:52 +13:00
|
|
|
announce = {
|
|
|
|
"id": f"https://{current_app.config['SERVER_NAME']}/activities/announce/{gibberish(15)}",
|
|
|
|
"type": 'Announce',
|
2023-12-08 17:13:38 +13:00
|
|
|
"to": [
|
|
|
|
"https://www.w3.org/ns/activitystreams#Public"
|
|
|
|
],
|
2023-12-26 21:39:52 +13:00
|
|
|
"actor": community.ap_profile_id,
|
2023-12-08 17:13:38 +13:00
|
|
|
"cc": [
|
2023-12-26 21:39:52 +13:00
|
|
|
community.ap_followers_url
|
2023-12-08 17:13:38 +13:00
|
|
|
],
|
2023-12-26 21:39:52 +13:00
|
|
|
'@context': default_context(),
|
|
|
|
'object': create
|
2023-12-08 17:13:38 +13:00
|
|
|
}
|
2023-12-26 21:39:52 +13:00
|
|
|
|
|
|
|
sent_to = 0
|
|
|
|
for instance in community.following_instances():
|
2024-01-03 16:29:58 +13:00
|
|
|
if instance.inbox and not current_user.has_blocked_instance(instance.id) and not instance_banned(instance.domain):
|
|
|
|
send_to_remote_instance(instance.id, community.id, announce)
|
2023-12-26 21:39:52 +13:00
|
|
|
sent_to += 1
|
|
|
|
if sent_to:
|
|
|
|
flash(_('Your post to %(name)s has been made.', name=community.title))
|
2023-12-22 15:34:45 +13:00
|
|
|
else:
|
2023-12-26 21:39:52 +13:00
|
|
|
flash(_('Your post to %(name)s has been made.', name=community.title))
|
2023-11-30 20:57:51 +13:00
|
|
|
|
2023-09-17 21:19:51 +12:00
|
|
|
return redirect(f"/c/{community.link()}")
|
|
|
|
else:
|
|
|
|
form.communities.data = community.id
|
2023-11-30 20:57:51 +13:00
|
|
|
form.notify_author.data = True
|
2023-09-17 21:19:51 +12:00
|
|
|
|
|
|
|
return render_template('community/add_post.html', title=_('Add post to community'), form=form, community=community,
|
2023-12-26 10:49:08 +13:00
|
|
|
images_disabled=images_disabled, markdown_editor=True)
|
2023-11-30 20:57:51 +13:00
|
|
|
|
|
|
|
|
2023-12-13 21:04:11 +13:00
|
|
|
@bp.route('/community/<int:community_id>/report', methods=['GET', 'POST'])
|
2023-12-26 21:39:52 +13:00
|
|
|
@login_required
|
2023-12-13 21:04:11 +13:00
|
|
|
def community_report(community_id: int):
|
|
|
|
community = Community.query.get_or_404(community_id)
|
|
|
|
form = ReportCommunityForm()
|
|
|
|
if form.validate_on_submit():
|
|
|
|
report = Report(reasons=form.reasons_to_string(form.reasons.data), description=form.description.data,
|
|
|
|
type=1, reporter_id=current_user.id, suspect_community_id=community.id)
|
|
|
|
db.session.add(report)
|
|
|
|
|
|
|
|
# Notify admin
|
|
|
|
# todo: find all instance admin(s). for now just load User.id == 1
|
|
|
|
admins = [User.query.get_or_404(1)]
|
|
|
|
for admin in admins:
|
|
|
|
notification = Notification(user_id=admin.id, title=_('A post has been reported'),
|
|
|
|
url=community.local_url(),
|
|
|
|
author_id=current_user.id)
|
|
|
|
db.session.add(notification)
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
# todo: federate report to originating instance
|
|
|
|
if not community.is_local() and form.report_remote.data:
|
|
|
|
...
|
2023-11-30 20:57:51 +13:00
|
|
|
|
2023-12-13 21:04:11 +13:00
|
|
|
flash(_('Community has been reported, thank you!'))
|
|
|
|
return redirect(community.local_url())
|
|
|
|
|
|
|
|
return render_template('community/community_report.html', title=_('Report community'), form=form, community=community)
|
|
|
|
|
|
|
|
|
2023-12-21 22:14:43 +13:00
|
|
|
@bp.route('/community/<int:community_id>/delete', methods=['GET', 'POST'])
|
2023-12-26 21:39:52 +13:00
|
|
|
@login_required
|
2023-12-21 22:14:43 +13:00
|
|
|
def community_delete(community_id: int):
|
|
|
|
community = Community.query.get_or_404(community_id)
|
|
|
|
if community.is_owner() or current_user.is_admin():
|
|
|
|
form = DeleteCommunityForm()
|
|
|
|
if form.validate_on_submit():
|
2024-01-01 11:38:24 +13:00
|
|
|
if community.is_local():
|
|
|
|
community.banned = True
|
|
|
|
# todo: federate deletion out to all instances. At end of federation process, delete_dependencies() and delete community
|
|
|
|
else:
|
|
|
|
community.delete_dependencies()
|
|
|
|
db.session.delete(community)
|
2023-12-21 22:14:43 +13:00
|
|
|
db.session.commit()
|
|
|
|
flash(_('Community deleted'))
|
|
|
|
return redirect('/communities')
|
|
|
|
|
|
|
|
return render_template('community/community_delete.html', title=_('Delete community'), form=form,
|
|
|
|
community=community)
|
|
|
|
else:
|
|
|
|
abort(401)
|
|
|
|
|
|
|
|
|
2023-12-13 21:04:11 +13:00
|
|
|
@bp.route('/community/<int:community_id>/block_instance', methods=['GET', 'POST'])
|
2023-12-26 21:39:52 +13:00
|
|
|
@login_required
|
2023-12-13 21:04:11 +13:00
|
|
|
def community_block_instance(community_id: int):
|
|
|
|
community = Community.query.get_or_404(community_id)
|
|
|
|
existing = InstanceBlock.query.filter_by(user_id=current_user.id, instance_id=community.instance_id).first()
|
|
|
|
if not existing:
|
|
|
|
db.session.add(InstanceBlock(user_id=current_user.id, instance_id=community.instance_id))
|
|
|
|
db.session.commit()
|
|
|
|
flash(_('Content from %(name)s will be hidden.', name=community.instance.domain))
|
|
|
|
return redirect(community.local_url())
|