diff --git a/app/activitypub/util.py b/app/activitypub/util.py index e3996ed5..425f5125 100644 --- a/app/activitypub/util.py +++ b/app/activitypub/util.py @@ -339,10 +339,19 @@ def refresh_user_profile_task(user_id): if actor_data.status_code == 200: activity_json = actor_data.json() actor_data.close() + + # update indexible state on their posts, if necessary + new_indexable = activity_json['indexable'] if 'indexable' in activity_json else True + if new_indexable != user.indexable: + db.session.execute(text('UPDATE "post" set indexable = :indexable WHERE user_id = :user_id'), + {'user_id': user.id, + 'indexable': new_indexable}) + user.user_name = activity_json['preferredUsername'] user.about_html = parse_summary(activity_json) user.ap_fetched_at = utcnow() - user.public_key=activity_json['publicKey']['publicKeyPem'] + user.public_key = activity_json['publicKey']['publicKeyPem'] + user.indexable = new_indexable avatar_changed = cover_changed = False if 'icon' in activity_json: @@ -594,6 +603,7 @@ def post_json_to_model(post_json, user, community) -> Post: last_active=post_json['published'], instance_id=user.instance_id ) + post.indexable = user.indexable if 'source' in post_json and \ post_json['source']['mediaType'] == 'text/markdown': post.body = post_json['source']['content'] @@ -1212,7 +1222,8 @@ def create_post(activity_log: ActivityPubLog, community: Community, request_json type=constants.POST_TYPE_ARTICLE, up_votes=1, score=instance_weight(user.ap_domain), - instance_id=user.instance_id + instance_id=user.instance_id, + indexable=user.indexable ) # Get post content. Lemmy and Kbin put this in different places. if 'source' in request_json['object'] and isinstance(request_json['object']['source'], dict) and request_json['object']['source']['mediaType'] == 'text/markdown': # Lemmy @@ -1227,7 +1238,10 @@ def create_post(activity_log: ActivityPubLog, community: Community, request_json post.url = request_json['object']['attachment'][0]['href'] if is_image_url(post.url): post.type = POST_TYPE_IMAGE - image = File(source_url=request_json['object']['image']['url']) + if 'image' in request_json['object'] and 'url' in request_json['object']['image']: + image = File(source_url=request_json['object']['image']['url']) + else: + image = File(source_url=post.url) db.session.add(image) post.image = image else: diff --git a/app/community/forms.py b/app/community/forms.py index ee514ad6..4b287a2c 100644 --- a/app/community/forms.py +++ b/app/community/forms.py @@ -127,6 +127,14 @@ class ReportCommunityForm(FlaskForm): report_remote = BooleanField('Also send report to originating instance') submit = SubmitField(_l('Report')) + def reasons_to_string(self, reason_data) -> str: + result = [] + for reason_id in reason_data: + for choice in self.reason_choices: + if choice[0] == reason_id: + result.append(str(choice[1])) + return ', '.join(result) + class DeleteCommunityForm(FlaskForm): submit = SubmitField(_l('Delete community')) diff --git a/app/community/routes.py b/app/community/routes.py index 9b9477d8..f9b58408 100644 --- a/app/community/routes.py +++ b/app/community/routes.py @@ -23,7 +23,7 @@ from app.community import bp from app.utils import get_setting, render_template, allowlist_html, markdown_to_html, validation_required, \ shorten_string, gibberish, community_membership, ap_datetime, \ request_etag_matches, return_304, instance_banned, can_create_post, can_upvote, can_downvote, user_filters_posts, \ - joined_communities, moderating_communities, blocked_domains, mimetype_from_url + joined_communities, moderating_communities, blocked_domains, mimetype_from_url, blocked_instances from feedgen.feed import FeedGenerator from datetime import timezone, timedelta @@ -152,9 +152,13 @@ def show_community(community: Community): posts = posts.filter(Post.nsfw == False) content_filters = user_filters_posts(current_user.id) + # filter domains and instances 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)) + instance_ids = blocked_instances(current_user.id) + if instance_ids: + posts = posts.filter(or_(Post.instance_id.not_in(instance_ids), Post.instance_id == None)) if sort == '' or sort == 'hot': posts = posts.order_by(desc(Post.ranking)).order_by(desc(Post.posted_at)) diff --git a/app/community/util.py b/app/community/util.py index 75132ded..f86e5811 100644 --- a/app/community/util.py +++ b/app/community/util.py @@ -164,6 +164,7 @@ def url_to_thumbnail_file(filename) -> File: def save_post(form, post: Post): + post.indexable = current_user.indexable post.nsfw = form.nsfw.data post.nsfl = form.nsfl.data post.notify_author = form.notify_author.data diff --git a/app/domain/routes.py b/app/domain/routes.py index 54a74a38..2cd68e3e 100644 --- a/app/domain/routes.py +++ b/app/domain/routes.py @@ -8,9 +8,9 @@ from app import db, constants, cache from app.inoculation import inoculation from app.models import Post, Domain, Community, DomainBlock from app.domain import bp -from app.utils import get_setting, render_template, permission_required, joined_communities, moderating_communities, \ - user_filters_posts, blocked_domains -from sqlalchemy import desc +from app.utils import render_template, permission_required, joined_communities, moderating_communities, \ + user_filters_posts, blocked_domains, blocked_instances +from sqlalchemy import desc, or_ @bp.route('/d/', methods=['GET']) @@ -31,6 +31,10 @@ def show_domain(domain_id): else: posts = Post.query.join(Community).filter(Post.domain_id == domain.id, Community.banned == False).order_by(desc(Post.posted_at)) + instance_ids = blocked_instances(current_user.id) + if instance_ids: + posts = posts.filter(or_(Post.instance_id.not_in(instance_ids), Post.instance_id == None)) + if current_user.is_authenticated: content_filters = user_filters_posts(current_user.id) else: @@ -77,7 +81,7 @@ def domains(): @bp.route('/domains/banned', methods=['GET']) @login_required -def blocked_domains(): +def domains_blocked_list(): if not current_user.trustworthy(): abort(404) diff --git a/app/main/routes.py b/app/main/routes.py index 9a2b3550..b2d292a0 100644 --- a/app/main/routes.py +++ b/app/main/routes.py @@ -23,7 +23,8 @@ from sqlalchemy import select, desc, text from sqlalchemy_searchable import search from app.utils import render_template, get_setting, gibberish, request_etag_matches, return_304, blocked_domains, \ 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, markdown_to_html, allowlist_html + joined_communities, moderating_communities, parse_page, theme_list, get_request, markdown_to_html, allowlist_html, \ + blocked_instances from app.models import Community, CommunityMember, Post, Site, User, utcnow, Domain, Topic, File, Instance, \ InstanceRole, Notification from PIL import Image @@ -103,6 +104,9 @@ def home_page(type, sort): 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)) + instance_ids = blocked_instances(current_user.id) + if instance_ids: + posts = posts.filter(or_(Post.instance_id.not_in(instance_ids), Post.instance_id == None)) content_filters = user_filters_home(current_user.id) # Sorting diff --git a/app/models.py b/app/models.py index c42f8a59..69449bd5 100644 --- a/app/models.py +++ b/app/models.py @@ -776,7 +776,7 @@ class Post(db.Model): nsfl = db.Column(db.Boolean, default=False, index=True) sticky = db.Column(db.Boolean, default=False) notify_author = db.Column(db.Boolean, default=True) - indexable = db.Column(db.Boolean, default=False) + indexable = db.Column(db.Boolean, default=True) from_bot = db.Column(db.Boolean, default=False, index=True) created_at = db.Column(db.DateTime, index=True, default=utcnow) # this is when the content arrived here posted_at = db.Column(db.DateTime, index=True, default=utcnow) # this is when the original server created it diff --git a/app/post/routes.py b/app/post/routes.py index e418eb60..eef451ea 100644 --- a/app/post/routes.py +++ b/app/post/routes.py @@ -7,7 +7,7 @@ from flask_login import login_user, logout_user, current_user, login_required from flask_babel import _ from sqlalchemy import or_, desc -from app import db, constants +from app import db, constants, cache from app.activitypub.signature import HttpSignature, post_request from app.activitypub.util import default_context from app.community.util import save_post, send_to_remote_instance @@ -22,7 +22,8 @@ from app.post import bp from app.utils import get_setting, render_template, allowlist_html, markdown_to_html, validation_required, \ shorten_string, markdown_to_text, gibberish, ap_datetime, return_304, \ request_etag_matches, ip_address, user_ip_banned, instance_banned, can_downvote, can_upvote, post_ranking, \ - reply_already_exists, reply_is_just_link_to_gif_reaction, confidence, moderating_communities, joined_communities + reply_already_exists, reply_is_just_link_to_gif_reaction, confidence, moderating_communities, joined_communities, \ + blocked_instances, blocked_domains def show_post(post_id: int): @@ -215,6 +216,7 @@ def show_post(post_id: int): canonical=post.ap_id, form=form, replies=replies, THREAD_CUTOFF_DEPTH=constants.THREAD_CUTOFF_DEPTH, description=description, og_image=og_image, POST_TYPE_IMAGE=constants.POST_TYPE_IMAGE, POST_TYPE_LINK=constants.POST_TYPE_LINK, POST_TYPE_ARTICLE=constants.POST_TYPE_ARTICLE, + noindex=not post.author.indexable, etag=f"{post.id}{sort}_{hash(post.last_active)}", markdown_editor=current_user.is_authenticated and current_user.markdown_editor, low_bandwidth=request.cookies.get('low_bandwidth', '0') == '1', SUBSCRIPTION_MEMBER=SUBSCRIPTION_MEMBER, moderating_communities=moderating_communities(current_user.get_id()), @@ -602,6 +604,7 @@ def post_reply_options(post_id: int, comment_id: int): def post_edit(post_id: int): post = Post.query.get_or_404(post_id) form = CreatePostForm() + del form.communities if post.user_id == current_user.id or post.community.is_moderator(): if g.site.enable_nsfl is False: form.nsfl.render_kw = {'disabled': True} @@ -612,7 +615,7 @@ def post_edit(post_id: int): form.nsfl.data = True form.nsfw.render_kw = {'disabled': True} - form.communities.choices = [(c.id, c.display_name()) for c in current_user.communities()] + #form.communities.choices = [(c.id, c.display_name()) for c in current_user.communities()] if form.validate_on_submit(): save_post(form, post) @@ -848,6 +851,7 @@ def post_block_domain(post_id: int): if not existing: db.session.add(DomainBlock(user_id=current_user.id, domain_id=post.domain_id)) db.session.commit() + cache.delete_memoized(blocked_domains, current_user.id) flash(_('Posts linking to %(name)s will be hidden.', name=post.domain.name)) return redirect(post.community.local_url()) @@ -860,6 +864,7 @@ def post_block_instance(post_id: int): if not existing: db.session.add(InstanceBlock(user_id=current_user.id, instance_id=post.instance_id)) db.session.commit() + cache.delete_memoized(blocked_instances, current_user.id) flash(_('Content from %(name)s will be hidden.', name=post.instance.domain)) return redirect(post.community.local_url()) diff --git a/app/post/util.py b/app/post/util.py index 1773c83b..948e3f1f 100644 --- a/app/post/util.py +++ b/app/post/util.py @@ -1,14 +1,20 @@ from typing import List -from sqlalchemy import desc, text +from flask_login import current_user +from sqlalchemy import desc, text, or_ from app import db from app.models import PostReply +from app.utils import blocked_instances # replies to a post, in a tree, sorted by a variety of methods def post_replies(post_id: int, sort_by: str, show_first: int = 0) -> List[PostReply]: comments = PostReply.query.filter_by(post_id=post_id) + if current_user.is_authenticated: + instance_ids = blocked_instances(current_user.id) + if instance_ids: + comments = comments.filter(or_(PostReply.instance_id.not_in(instance_ids), PostReply.instance_id == None)) if sort_by == 'hot': comments = comments.order_by(desc(PostReply.ranking)) elif sort_by == 'top': @@ -36,6 +42,10 @@ def get_comment_branch(post_id: int, comment_id: int, sort_by: str) -> List[Post return [] comments = PostReply.query.filter(PostReply.post_id == post_id) + if current_user.is_authenticated: + instance_ids = blocked_instances(current_user.id) + if instance_ids: + comments = comments.filter(or_(PostReply.instance_id.not_in(instance_ids), PostReply.instance_id == None)) if sort_by == 'hot': comments = comments.order_by(desc(PostReply.ranking)) elif sort_by == 'top': diff --git a/app/search/routes.py b/app/search/routes.py index e4a96cf8..8edbcef7 100644 --- a/app/search/routes.py +++ b/app/search/routes.py @@ -5,11 +5,10 @@ from sqlalchemy import or_ from app.models import Post from app.search import bp -from app.utils import moderating_communities, joined_communities, render_template, blocked_domains +from app.utils import moderating_communities, joined_communities, render_template, blocked_domains, blocked_instances @bp.route('/search', methods=['GET', 'POST']) -@login_required def run_search(): if request.args.get('q') is not None: q = request.args.get('q') @@ -17,16 +16,26 @@ def run_search(): low_bandwidth = request.cookies.get('low_bandwidth', '0') == '1' posts = Post.query.search(q) - if current_user.ignore_bots: + if current_user.is_authenticated: + 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) + 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)) + instance_ids = blocked_instances(current_user.id) + if instance_ids: + posts = posts.filter(or_(Post.instance_id.not_in(instance_ids), Post.instance_id == None)) + else: 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) - 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)) + posts = posts.filter(Post.indexable == True) + posts = posts.paginate(page=page, per_page=100 if current_user.is_authenticated and not low_bandwidth else 50, error_out=False) diff --git a/app/templates/base.html b/app/templates/base.html index 92c8e266..7e76319b 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -70,6 +70,9 @@ {% if rss_feed %} {% endif %} + {% if noindex %} + + {% endif %}