pyfedi/app/shared/tasks/follows.py
2024-11-02 23:56:56 +00:00

165 lines
5.4 KiB
Python

from app import cache, celery, db
from app.activitypub.signature import default_context, post_request
from app.models import Community, CommunityBan, CommunityJoinRequest, User
from app.utils import community_membership, gibberish, joined_communities, instance_banned
from flask import current_app, flash
from flask_babel import _
# would be in app/constants.py
SRC_WEB = 1
SRC_PUB = 2
SRC_API = 3
SRC_PLD = 4
""" JSON format
{
'id':
'type':
'actor':
'object':
'@context': (outer object only)
'to': []
}
"""
"""
async:
delete memoized
add or delete community_join_request
used for admin preload in production (return values are ignored)
used for API
sync:
add or delete community_member
used for debug mode
used for web users to provide feedback
"""
@celery.task
def join_community(send_async, user_id, community_id, src):
user = User.query.filter_by(id=user_id).one()
community = Community.query.filter_by(id=community_id).one()
pre_load_message = {}
banned = CommunityBan.query.filter_by(user_id=user_id, community_id=community_id).first()
if banned:
if not send_async:
if src == SRC_WEB:
flash(_('You cannot join this community'))
return
elif src == SRC_PLD:
pre_load_message['user_banned'] = True
return pre_load_message
elif src == SRC_API:
raise Exception('banned_from_community')
return
if (not community.is_local() and
(user.has_blocked_instance(community.instance.id) or
instance_banned(community.instance.domain))):
if not send_async:
if src == SRC_WEB:
flash(_('Community is on banned or blocked instance'))
return
elif src == SRC_PLD:
pre_load_message['community_on_banned_or_blocked_instance'] = True
return pre_load_message
elif src == SRC_API:
raise Exception('community_on_banned_or_blocked_instance')
return
success = True
if not community.is_local() and community.instance.online():
join_request = CommunityJoinRequest(user_id=user_id, community_id=community_id)
db.session.add(join_request)
db.session.commit()
follow_id = f"https://{current_app.config['SERVER_NAME']}/activities/follow/{join_request.id}"
follow = {
'id': follow_id,
'type': 'Follow',
'actor': user.public_url(),
'object': community.public_url(),
'@context': default_context(),
'to': [community.public_url()],
}
success = post_request(community.ap_inbox_url, follow, user.private_key,
user.public_url() + '#main-key', timeout=10)
if success is False or isinstance(success, str):
if not send_async:
db.session.query(CommunityJoinRequest).filter_by(user_id=user_id, community_id=community_id).delete()
db.session.commit()
if 'is not in allowlist' in success:
msg_to_user = f'{community.instance.domain} does not allow us to join their communities.'
else:
msg_to_user = "There was a problem while trying to communicate with remote server. Please try again later."
if src == SRC_WEB:
flash(_(msg_to_user), 'error')
return
elif src == SRC_PLD:
pre_load_message['status'] = msg_to_user
return pre_load_message
elif src == SRC_API:
raise Exception(msg_to_user)
# for communities on local or offline instances, joining is instant
if success is True:
cache.delete_memoized(community_membership, user, community)
cache.delete_memoized(joined_communities, user.id)
if src == SRC_WEB:
flash('You joined ' + community.title)
return
elif src == SRC_PLD:
pre_load_message['status'] = 'joined'
return pre_load_message
return success
@celery.task
def leave_community(send_async, user_id, community_id):
user = User.query.filter_by(id=user_id).one()
community = Community.query.filter_by(id=community_id).one()
cache.delete_memoized(community_membership, user, community)
cache.delete_memoized(joined_communities, user.id)
if community.is_local():
return
join_request = CommunityJoinRequest.query.filter_by(user_id=user_id, community_id=community_id).one()
db.session.delete(join_request)
db.session.commit()
if (not community.instance.online() or
user.has_blocked_instance(community.instance.id) or
instance_banned(community.instance.domain)):
return
follow_id = f"https://{current_app.config['SERVER_NAME']}/activities/follow/{join_request.id}"
follow = {
'id': follow_id,
'type': 'Follow',
'actor': user.public_url(),
'object': community.public_url(),
'to': [community.public_url()]
}
undo_id = f"https://{current_app.config['SERVER_NAME']}/activities/undo/{gibberish(15)}"
undo = {
'id': undo_id,
'type': 'Undo',
'actor': user.public_url(),
'object': follow,
'@context': default_context(),
'to': [community.public_url()]
}
post_request(community.ap_inbox_url, undo, user.private_key, user.public_url() + '#main-key', timeout=10)