diff --git a/app/community/routes.py b/app/community/routes.py index d1118dac..4f6c8962 100644 --- a/app/community/routes.py +++ b/app/community/routes.py @@ -36,7 +36,6 @@ from app.models import User, Community, CommunityMember, CommunityJoinRequest, C NotificationSubscription, UserFollower, Instance, Language, Poll, PollChoice, ModLog, CommunityWikiPage, \ CommunityWikiPageRevision, read_posts from app.community import bp -from app.user.utils import search_for_user 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, \ diff --git a/app/templates/post/_post_reply_teaser.html b/app/templates/post/_post_reply_teaser.html index 6cd6906b..43a6544d 100644 --- a/app/templates/post/_post_reply_teaser.html +++ b/app/templates/post/_post_reply_teaser.html @@ -60,7 +60,7 @@

Deleted by author

{% endif -%} {% else -%} - {{ post_reply.body_html | community_links | safe }} + {{ post_reply.body_html | community_links | person_links | safe }} {% endif -%} diff --git a/app/user/routes.py b/app/user/routes.py index 1749649b..a344d623 100644 --- a/app/user/routes.py +++ b/app/user/routes.py @@ -21,7 +21,7 @@ from app.models import Post, Community, CommunityMember, User, PostReply, PostVo from app.user import bp from app.user.forms import ProfileForm, SettingsForm, DeleteAccountForm, ReportUserForm, \ FilterForm, KeywordFilterEditForm, RemoteFollowForm, ImportExportForm, UserNoteForm -from app.user.utils import purge_user_then_delete, unsubscribe_from_community +from app.user.utils import purge_user_then_delete, unsubscribe_from_community, search_for_user from app.utils import get_setting, render_template, markdown_to_html, user_access, markdown_to_text, shorten_string, \ is_image_url, ensure_directory_exists, gibberish, file_get_contents, community_membership, user_filters_home, \ user_filters_posts, user_filters_replies, moderating_communities, joined_communities, theme_list, blocked_instances, \ @@ -1381,3 +1381,40 @@ def user_preview(user_id): if (user.deleted or user.banned) and current_user.is_anonymous: abort(404) return render_template('user/user_preview.html', user=user) + + +@bp.route('/user/lookup//') +def lookup(person, domain): + if domain == current_app.config['SERVER_NAME']: + return redirect('/u/' + person) + + exists = User.query.filter_by(user_name=person, ap_domain=domain).first() + if exists: + return redirect('/u/' + person + '@' + domain) + else: + address = '@' + person + '@' + domain + if current_user.is_authenticated: + new_person = None + + try: + new_person = search_for_user(address) + except Exception as e: + if 'is blocked.' in str(e): + flash(_('Sorry, that instance is blocked, check https://gui.fediseer.com/ for reasons.'), 'warning') + if not new_person or new_person.banned: + flash(_('That person could not be retreived or is banned from %(site)s.', site=g.site.name), 'warning') + referrer = request.headers.get('Referer', None) + if referrer is not None: + return redirect(referrer) + else: + return redirect('/') + + return redirect('/u/' + new_person.ap_id) + else: + # send them back where they came from + flash('Searching for remote people requires login', 'error') + referrer = request.headers.get('Referer', None) + if referrer is not None: + return redirect(referrer) + else: + return redirect('/') diff --git a/app/user/utils.py b/app/user/utils.py index 6bf77760..ef6eaa77 100644 --- a/app/user/utils.py +++ b/app/user/utils.py @@ -3,12 +3,14 @@ from time import sleep from flask import current_app, json from app import celery, db -from app.activitypub.signature import post_request, default_context +from app.activitypub.signature import post_request, default_context, signed_get_request from app.activitypub.util import actor_json_to_model from app.community.util import send_to_remote_instance from app.models import User, CommunityMember, Community, Instance, Site, utcnow, ActivityPubLog, BannedInstances from app.utils import gibberish, ap_datetime, instance_banned, get_request +import httpx + def purge_user_then_delete(user_id): if current_app.debug: @@ -140,17 +142,47 @@ def search_for_user(address: str): webfinger_data = get_request(f"https://{server}/.well-known/webfinger", params={'resource': f"acct:{address}"}) if webfinger_data.status_code == 200: - webfinger_json = webfinger_data.json() + try: + webfinger_json = webfinger_data.json() + webfinger_data.close() + except: + webfinger_data.close() + return None for links in webfinger_json['links']: if 'rel' in links and links['rel'] == 'self': # this contains the URL of the activitypub profile type = links['type'] if 'type' in links else 'application/activity+json' # retrieve the activitypub profile - user_data = get_request(links['href'], headers={'Accept': type}) - # to see the structure of the json contained in community_data, do a GET to https://lemmy.world/c/technology with header Accept: application/activity+json - if user_data.status_code == 200: - user_json = user_data.json() - user_data.close() - if user_json['type'] == 'Person' or user_json['type'] == 'Service': - user = actor_json_to_model(user_json, name, server) - return user + for attempt in [1,2]: + try: + object_request = get_request(links['href'], headers={'Accept': type}) + except httpx.HTTPError: + if attempt == 1: + time.sleep(3) + else: + return None + if object_request.status_code == 401: + site = Site.query.get(1) + for attempt in [1,2]: + try: + object_request = signed_get_request(links['href'], site.private_key, f"https://{current_app.config['SERVER_NAME']}/actor#main-key") + except httpx.HTTPError: + if attempt == 1: + time.sleep(3) + else: + return None + if object_request.status_code == 200: + try: + object = object_request.json() + object_request.close() + except: + object_request.close() + return None + else: + return None + + if object['type'] == 'Person' or object['type'] == 'Service': + user = actor_json_to_model(object, name, server) + return user + return None + diff --git a/app/utils.py b/app/utils.py index 77ccbe7a..f35beabd 100644 --- a/app/utils.py +++ b/app/utils.py @@ -456,6 +456,12 @@ def community_link_to_href(link: str) -> str: return re.sub(pattern, server + r'\g<1>/\g<2>>' + r'!\g<1>@\g<2>', link) +def person_link_to_href(link: str) -> str: + pattern = r"@([a-zA-Z0-9_.-]*)@([a-zA-Z0-9_.-]*)\b" + server = r'/\g<2>>' + r'@\g<1>@\g<2>', link) + + def domain_from_url(url: str, create=True) -> Domain: parsed_url = urlparse(url.lower().replace('www.', '')) if parsed_url and parsed_url.hostname: diff --git a/pyfedi.py b/pyfedi.py index 55132492..62df556d 100644 --- a/pyfedi.py +++ b/pyfedi.py @@ -13,7 +13,7 @@ from app.constants import POST_TYPE_LINK, POST_TYPE_IMAGE, POST_TYPE_ARTICLE, PO from app.models import Site from app.utils import getmtime, gibberish, shorten_string, shorten_url, digits, user_access, community_membership, \ can_create_post, can_upvote, can_downvote, shorten_number, ap_datetime, current_theme, community_link_to_href, \ - in_sorted_list, role_access, first_paragraph + in_sorted_list, role_access, first_paragraph, person_link_to_href app = create_app() cli.register(app) @@ -54,6 +54,7 @@ with app.app_context(): app.jinja_env.globals['file_exists'] = os.path.exists app.jinja_env.globals['first_paragraph'] = first_paragraph app.jinja_env.filters['community_links'] = community_link_to_href + app.jinja_env.filters['person_links'] = person_link_to_href app.jinja_env.filters['shorten'] = shorten_string app.jinja_env.filters['shorten_url'] = shorten_url