2024-11-02 23:56:56 +00:00
|
|
|
from app import cache, celery, db
|
|
|
|
from app.activitypub.signature import default_context, post_request
|
|
|
|
from app.models import Community, CommunityBan, CommunityJoinRequest, CommunityMember, Notification, Post, PostReply, User, utcnow
|
|
|
|
from app.user.utils import search_for_user
|
|
|
|
from app.utils import community_membership, gibberish, joined_communities, instance_banned, ap_datetime, \
|
|
|
|
recently_upvoted_posts, recently_downvoted_posts, recently_upvoted_post_replies, recently_downvoted_post_replies
|
|
|
|
|
|
|
|
from flask import current_app
|
|
|
|
from flask_babel import _
|
|
|
|
|
|
|
|
import re
|
|
|
|
|
|
|
|
|
|
|
|
""" Reply JSON format
|
|
|
|
{
|
|
|
|
'id':
|
|
|
|
'url':
|
|
|
|
'type':
|
|
|
|
'attributedTo':
|
|
|
|
'to': []
|
|
|
|
'cc': []
|
|
|
|
'tag': []
|
|
|
|
'audience':
|
|
|
|
'content':
|
|
|
|
'mediaType':
|
|
|
|
'source': {}
|
|
|
|
'inReplyTo':
|
|
|
|
'published':
|
|
|
|
'updated': (inner oject of Update only)
|
|
|
|
'language': {}
|
|
|
|
'contentMap':{}
|
|
|
|
'distinguished'
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
""" Create / Update / Announce JSON format
|
|
|
|
{
|
|
|
|
'id':
|
|
|
|
'type':
|
|
|
|
'actor':
|
|
|
|
'object':
|
|
|
|
'to': []
|
|
|
|
'cc': []
|
|
|
|
'@context': (outer object only)
|
|
|
|
'audience': (not in Announce)
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@celery.task
|
|
|
|
def make_reply(send_async, user_id, reply_id, parent_id):
|
|
|
|
send_reply(user_id, reply_id, parent_id)
|
|
|
|
|
|
|
|
|
|
|
|
@celery.task
|
|
|
|
def edit_reply(send_async, user_id, reply_id, parent_id):
|
|
|
|
send_reply(user_id, reply_id, parent_id, edit=True)
|
|
|
|
|
|
|
|
|
|
|
|
def send_reply(user_id, reply_id, parent_id, edit=False):
|
|
|
|
user = User.query.filter_by(id=user_id).one()
|
|
|
|
reply = PostReply.query.filter_by(id=reply_id).one()
|
|
|
|
if parent_id:
|
|
|
|
parent = PostReply.query.filter_by(id=parent_id).one()
|
|
|
|
else:
|
|
|
|
parent = reply.post
|
|
|
|
community = reply.community
|
|
|
|
|
2025-01-10 14:03:00 +00:00
|
|
|
# Find any users Mentioned in reply with @user@instance syntax
|
2024-11-02 23:56:56 +00:00
|
|
|
recipients = [parent.author]
|
|
|
|
pattern = r"@([a-zA-Z0-9_.-]*)@([a-zA-Z0-9_.-]*)\b"
|
|
|
|
matches = re.finditer(pattern, reply.body)
|
|
|
|
for match in matches:
|
|
|
|
recipient = None
|
|
|
|
if match.group(2) == current_app.config['SERVER_NAME']:
|
|
|
|
user_name = match.group(1)
|
2025-01-10 14:03:00 +00:00
|
|
|
if user_name != user.user_name:
|
|
|
|
try:
|
|
|
|
recipient = search_for_user(user_name)
|
|
|
|
except:
|
|
|
|
pass
|
2024-11-02 23:56:56 +00:00
|
|
|
else:
|
|
|
|
ap_id = f"{match.group(1)}@{match.group(2)}"
|
|
|
|
try:
|
|
|
|
recipient = search_for_user(ap_id)
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
if recipient:
|
|
|
|
add_recipient = True
|
|
|
|
for existing_recipient in recipients:
|
|
|
|
if ((not recipient.ap_id and recipient.user_name == existing_recipient.user_name) or
|
|
|
|
(recipient.ap_id and recipient.ap_id == existing_recipient.ap_id)):
|
|
|
|
add_recipient = False
|
|
|
|
break
|
|
|
|
if add_recipient:
|
|
|
|
recipients.append(recipient)
|
|
|
|
|
2025-01-10 14:03:00 +00:00
|
|
|
# Notify any local users that have been Mentioned
|
|
|
|
for recipient in recipients:
|
|
|
|
if recipient.is_local() and recipient.id != parent.author.id:
|
|
|
|
if edit:
|
|
|
|
existing_notification = Notification.query.filter(Notification.user_id == recipient.id, Notification.url == f"https://{current_app.config['SERVER_NAME']}/comment/{reply.id}").first()
|
|
|
|
else:
|
|
|
|
existing_notification = None
|
|
|
|
if not existing_notification:
|
|
|
|
notification = Notification(user_id=recipient.id, title=_(f"You have been mentioned in comment {reply.id}"),
|
|
|
|
url=f"https://{current_app.config['SERVER_NAME']}/comment/{reply.id}",
|
|
|
|
author_id=user.id)
|
|
|
|
recipient.unread_notifications += 1
|
|
|
|
db.session.add(notification)
|
|
|
|
db.session.commit()
|
2024-11-02 23:56:56 +00:00
|
|
|
|
|
|
|
if community.local_only or not community.instance.online():
|
|
|
|
return
|
|
|
|
|
|
|
|
banned = CommunityBan.query.filter_by(user_id=user_id, community_id=community.id).first()
|
|
|
|
if banned:
|
|
|
|
return
|
|
|
|
if not community.is_local():
|
|
|
|
if user.has_blocked_instance(community.instance.id) or instance_banned(community.instance.domain):
|
|
|
|
return
|
|
|
|
|
|
|
|
to = ["https://www.w3.org/ns/activitystreams#Public"]
|
|
|
|
cc = [community.public_url()]
|
|
|
|
tag = []
|
|
|
|
for recipient in recipients:
|
|
|
|
tag.append({'href': recipient.public_url(), 'name': recipient.mention_tag(), 'type': 'Mention'})
|
|
|
|
cc.append(recipient.public_url())
|
|
|
|
language = {'identifier': reply.language_code(), 'name': reply.language_name()}
|
|
|
|
content_map = {reply.language_code(): reply.body_html}
|
|
|
|
source = {'content': reply.body, 'mediaType': 'text/markdown'}
|
|
|
|
note = {
|
|
|
|
'id': reply.public_url(),
|
|
|
|
'url': reply.public_url(),
|
|
|
|
'type': 'Note',
|
|
|
|
'attributedTo': user.public_url(),
|
|
|
|
'to': to,
|
|
|
|
'cc': cc,
|
|
|
|
'tag': tag,
|
|
|
|
'audience': community.public_url(),
|
|
|
|
'content': reply.body_html,
|
|
|
|
'mediaType': 'text/html',
|
|
|
|
'source': source,
|
|
|
|
'inReplyTo': parent.public_url(),
|
|
|
|
'published': ap_datetime(reply.posted_at),
|
|
|
|
'language': language,
|
|
|
|
'contentMap': content_map,
|
|
|
|
'distinguished': False,
|
|
|
|
}
|
|
|
|
if edit:
|
2025-01-17 17:18:38 +00:00
|
|
|
note['updated'] = ap_datetime(utcnow())
|
2024-11-02 23:56:56 +00:00
|
|
|
|
|
|
|
activity = 'create' if not edit else 'update'
|
|
|
|
create_id = f"https://{current_app.config['SERVER_NAME']}/activities/{activity}/{gibberish(15)}"
|
|
|
|
type = 'Create' if not edit else 'Update'
|
|
|
|
create = {
|
|
|
|
'id': create_id,
|
|
|
|
'type': type,
|
|
|
|
'actor': user.public_url(),
|
|
|
|
'object': note,
|
|
|
|
'to': to,
|
|
|
|
'cc': cc,
|
|
|
|
'@context': default_context(),
|
2025-01-17 19:59:45 +00:00
|
|
|
'audience': community.public_url()
|
2024-11-02 23:56:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
domains_sent_to = [current_app.config['SERVER_NAME']]
|
|
|
|
|
2025-01-10 14:03:00 +00:00
|
|
|
# send the activity as an Announce if the community is local, or as a Create if not
|
2024-11-02 23:56:56 +00:00
|
|
|
if community.is_local():
|
|
|
|
del create['@context']
|
|
|
|
|
|
|
|
announce_id = f"https://{current_app.config['SERVER_NAME']}/activities/announce/{gibberish(15)}"
|
|
|
|
actor = community.public_url()
|
|
|
|
cc = [community.ap_followers_url]
|
|
|
|
announce = {
|
|
|
|
'id': announce_id,
|
|
|
|
'type': 'Announce',
|
|
|
|
'actor': community.public_url(),
|
|
|
|
'object': create,
|
|
|
|
'to': to,
|
|
|
|
'cc': cc,
|
|
|
|
'@context': default_context()
|
|
|
|
}
|
|
|
|
for instance in community.following_instances():
|
|
|
|
if instance.inbox and instance.online() and not user.has_blocked_instance(instance.id) and not instance_banned(instance.domain):
|
|
|
|
post_request(instance.inbox, announce, community.private_key, community.public_url() + '#main-key')
|
|
|
|
domains_sent_to.append(instance.domain)
|
|
|
|
else:
|
|
|
|
post_request(community.ap_inbox_url, create, user.private_key, user.public_url() + '#main-key')
|
|
|
|
domains_sent_to.append(community.instance.domain)
|
|
|
|
|
2025-01-10 14:03:00 +00:00
|
|
|
# send copy of the Create to anyone else Mentioned in reply, but not on an instance that's already sent to.
|
|
|
|
if '@context' not in create:
|
|
|
|
create['@context'] = default_context()
|
2024-11-02 23:56:56 +00:00
|
|
|
for recipient in recipients:
|
|
|
|
if recipient.instance.domain not in domains_sent_to:
|
|
|
|
post_request(recipient.instance.inbox, create, user.private_key, user.public_url() + '#main-key')
|
|
|
|
|
|
|
|
|
|
|
|
|