mentions in comments - recognise @user@instance syntax in UI

This commit is contained in:
freamon 2025-01-10 12:30:30 +00:00
parent 250405aafb
commit 385bd6bfbe
6 changed files with 89 additions and 14 deletions

View file

@ -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, \

View file

@ -60,7 +60,7 @@
<p>Deleted by author</p>
{% endif -%}
{% else -%}
{{ post_reply.body_html | community_links | safe }}
{{ post_reply.body_html | community_links | person_links | safe }}
{% endif -%}
</div>
</div>

View file

@ -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/<person>/<domain>')
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('/')

View file

@ -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

View file

@ -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></a>', link)
def person_link_to_href(link: str) -> str:
pattern = r"@([a-zA-Z0-9_.-]*)@([a-zA-Z0-9_.-]*)\b"
server = r'<a href=https://' + current_app.config['SERVER_NAME'] + r'/user/lookup/'
return re.sub(pattern, server + r'\g<1>/\g<2>>' + r'@\g<1>@\g<2></a>', link)
def domain_from_url(url: str, create=True) -> Domain:
parsed_url = urlparse(url.lower().replace('www.', ''))
if parsed_url and parsed_url.hostname:

View file

@ -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