Retrieve remote post from Search (no pre-supplied community required)

This commit is contained in:
freamon 2024-05-28 22:28:03 +01:00
parent 788cad653b
commit ce1d01de09
4 changed files with 128 additions and 2 deletions

View file

@ -2430,3 +2430,106 @@ def resolve_remote_post(uri: str, community_id: int, announce_actor=None) -> Uni
return post
return None
def resolve_remote_post_from_search(uri: str) -> Union[Post, None]:
post = Post.query.filter_by(ap_id=uri).first()
if post:
return post
site = Site.query.get(1)
parsed_url = urlparse(uri)
uri_domain = parsed_url.netloc
actor_domain = None
actor = None
post_request = get_request(uri, headers={'Accept': 'application/activity+json'})
if post_request.status_code == 200:
post_data = post_request.json()
post_request.close()
# check again that it doesn't already exist (can happen with different but equivalent URLs)
post = Post.query.filter_by(ap_id=post_data['id']).first()
if post:
return post
# find the author of the post. Make sure their domain matches the site hosting it to migitage impersonation attempts
if 'attributedTo' in post_data:
if isinstance(post_data['attributedTo'], str):
actor = post_data['attributedTo']
parsed_url = urlparse(post_data['attributedTo'])
actor_domain = parsed_url.netloc
elif isinstance(post_data['attributedTo'], list):
for a in post_data['attributedTo']:
if a['type'] == 'Person':
actor = a['id']
parsed_url = urlparse(a['id'])
actor_domain = parsed_url.netloc
break
if uri_domain != actor_domain:
return None
# find the community the post was submitted to
community = None
if not community and post_data['type'] == 'Page': # lemmy
if 'audience' in post_data:
community_id = post_data['audience']
community = Community.query.filter_by(ap_profile_id=community_id).first()
if not community and post_data['type'] == 'Video': # peertube
if 'attributedTo' in post_data and isinstance(post_data['attributedTo'], list):
for a in post_data['attributedTo']:
if a['type'] == 'Group':
community_id = a['id']
community = Community.query.filter_by(ap_profile_id=community_id).first()
if community:
break
if not community: # mastodon, etc
if 'inReplyTo' not in post_data or post_data['inReplyTo'] != None:
return None
if not community and 'to' in post_data and isinstance(post_data['to'], str):
community_id = post_data['to'].lower()
if not community_id == 'https://www.w3.org/ns/activitystreams#Public' and not community_id.endswith('/followers'):
community = Community.query.filter_by(ap_profile_id=community_id).first()
if not community and 'cc' in post_data and isinstance(post_data['cc'], str):
community_id = post_data['cc'].lower()
if not community_id == 'https://www.w3.org/ns/activitystreams#Public' and not community_id.endswith('/followers'):
community = Community.query.filter_by(ap_profile_id=community_id).first()
if not community and 'to' in post_data and isinstance(post_data['to'], list):
for t in post_data['to']:
community_id = t.lower()
if not community_id == 'https://www.w3.org/ns/activitystreams#Public' and not community_id.endswith('/followers'):
community = Community.query.filter_by(ap_profile_id=community_id).first()
if community:
break
if not community and 'cc' in post_data and isinstance(post_data['to'], list):
for c in post_data['cc']:
community_id = c.lower()
if not community_id == 'https://www.w3.org/ns/activitystreams#Public' and not community_id.endswith('/followers'):
community = Community.query.filter_by(ap_profile_id=community_id).first()
if community:
break
if not community:
return None
activity_log = ActivityPubLog(direction='in', activity_id=post_data['id'], activity_type='Resolve Post', result='failure')
if site.log_activitypub_json:
activity_log.activity_json = json.dumps(post_data)
db.session.add(activity_log)
user = find_actor_or_create(actor)
if user and community and post_data:
request_json = {
'id': f"https://{uri_domain}/activities/create/gibberish(15)",
'object': post_data
}
post = create_post(activity_log, community, request_json, user)
if post:
if 'published' in post_data:
post.posted_at=post_data['published']
post.last_active=post_data['published']
db.session.commit()
return post
return None

View file

@ -7,6 +7,8 @@ from app.models import Post, Language, Community
from app.search import bp
from app.utils import moderating_communities, joined_communities, render_template, blocked_domains, blocked_instances, \
communities_banned_from, recently_upvoted_posts, recently_downvoted_posts, blocked_users
from app.community.forms import RetrieveRemotePost
from app.activitypub.util import resolve_remote_post_from_search
@bp.route('/search', methods=['GET', 'POST'])
@ -87,3 +89,20 @@ def run_search():
moderating_communities=moderating_communities(current_user.get_id()),
joined_communities=joined_communities(current_user.get_id()),
site=g.site)
@bp.route('/retrieve_remote_post', methods=['GET', 'POST'])
@login_required
def retrieve_remote_post():
if current_user.banned:
return show_ban_message()
form = RetrieveRemotePost()
new_post = None
if form.validate_on_submit():
address = form.address.data.strip()
new_post = resolve_remote_post_from_search(address)
if new_post is None:
flash(_('Post not found.'), 'warning')
return render_template('community/retrieve_remote_post.html',
title=_('Retrieve Remote Post'), form=form, new_post=new_post)

View file

@ -11,7 +11,11 @@
<div class="card mt-5">
<div class="card-body p-6">
<div class="card-title">{{ _('Retrieve Remote Post') }}</div>
{% if community %}
<p>Enter the full URL of the post submitted to {{ community.ap_id }}</p>
{% else %}
<p>Enter the full URL of the post</p>
{% endif %}
<p>Note: URL needs to match the one from the post author's instance (which may be different than the community's instance)</p>
{{ render_form(form) }}
</div>

View file

@ -55,7 +55,7 @@
<h6 class="mt-5">{{ _('Trying to add a communitiy or post from another instance?') }} </h6>
<p>{{ _('In many types of federated platforms you can put a URL of a post or community into the search in order to add it to your local instance. In PieFed the search is just for searching.') }}</p>
<p><a href="{{ url_for('community.add_remote') }}" class="btn btn-primary">{{ _('Add remote community') }}</a></p>
<p>{{ _('To add a post from a remote instance, find the community in PieFed then look for the "Retrieve a post from the original server" link.') }}</p>
<p><a href="{{ url_for('search.retrieve_remote_post') }}" class="btn btn-primary">{{ _('Retrieve remote post') }}</a></p>
</div>
</div>
</div>