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