diff --git a/blueprints/api.py b/blueprints/api.py index 2fe03f6..cb71d39 100644 --- a/blueprints/api.py +++ b/blueprints/api.py @@ -31,6 +31,7 @@ from config import JWT from config import MEDIA_CACHE from config import _drop_db from core import feed +from core.activitypub import accept_follow from core.activitypub import activity_url from core.activitypub import new_context from core.activitypub import post_to_outbox @@ -353,6 +354,24 @@ def api_undo() -> _Response: return _user_api_response(activity=undo_id) +@blueprint.route("/accept_follow", methods=["POST"]) +@api_required +def api_accept_follow() -> _Response: + oid = _user_api_arg("id") + doc = DB.activities.find_one({"box": Box.INBOX.value, "remote_id": oid}) + print(doc) + if not doc: + raise ActivityNotFoundError(f"cannot found {oid}") + + obj = ap.parse_activity(doc.get("activity")) + if not obj.has_type(ap.ActivityType.FOLLOW): + raise ValueError(f"{obj} is not a Follow activity") + + accept_id = accept_follow(obj) + + return _user_api_response(activity=accept_id) + + @blueprint.route("/new_list", methods=["POST"]) @api_required def api_new_list() -> _Response: diff --git a/config.py b/config.py index eefbf0b..8e93ca9 100644 --- a/config.py +++ b/config.py @@ -137,6 +137,8 @@ if PROFILE_METADATA: {"type": "PropertyValue", "name": key, "value": linkify(value)} ) +MANUALLY_APPROVES_FOLLOWERS = bool(conf.get("manually_approves_followers", False)) + ME = { "@context": DEFAULT_CTX, "type": "Person", @@ -151,7 +153,7 @@ ME = { "summary": SUMMARY, "endpoints": {}, "url": ID, - "manuallyApprovesFollowers": False, + "manuallyApprovesFollowers": MANUALLY_APPROVES_FOLLOWERS, "attachment": attachments, "icon": { "mediaType": mimetypes.guess_type(ICON_URL)[0], diff --git a/core/activitypub.py b/core/activitypub.py index ad0c74c..bd5ce4d 100644 --- a/core/activitypub.py +++ b/core/activitypub.py @@ -825,3 +825,24 @@ def handle_replies(create: ap.Create) -> None: # Spawn a task to process it (and determine if it needs to be saved) Tasks.process_reply(create.get_object_id()) + + +def accept_follow(activity: ap.BaseActivity) -> str: + actor_id = activity.get_actor().id + accept = ap.Accept( + actor=ID, + context=new_context(activity), + object={ + "type": "Follow", + "id": activity.id, + "object": activity.get_object_id(), + "actor": actor_id, + }, + to=[actor_id], + published=now(), + ) + update_one_activity( + by_remote_id(activity.id), + upsert({MetaKey.FOLLOW_STATUS: FollowStatus.ACCEPTED.value}), + ) + return post_to_outbox(accept) diff --git a/core/inbox.py b/core/inbox.py index 0a76fb6..9ebf78c 100644 --- a/core/inbox.py +++ b/core/inbox.py @@ -8,9 +8,8 @@ from little_boxes.errors import NotAnActivityError import config from core.activitypub import _answer_key +from core.activitypub import accept_follow from core.activitypub import handle_replies -from core.activitypub import new_context -from core.activitypub import post_to_outbox from core.activitypub import update_cached_actor from core.db import DB from core.db import update_one_activity @@ -23,7 +22,6 @@ from core.meta import in_inbox from core.meta import inc from core.meta import upsert from core.tasks import Tasks -from utils import now _logger = logging.getLogger(__name__) @@ -179,21 +177,14 @@ def _like_process_inbox(like: ap.Like, new_meta: _NewMeta) -> None: @process_inbox.register def _follow_process_inbox(activity: ap.Follow, new_meta: _NewMeta) -> None: _logger.info(f"process_inbox activity={activity!r}") - # Reply to a Follow with an Accept - actor_id = activity.get_actor().id - accept = ap.Accept( - actor=config.ID, - context=new_context(activity), - object={ - "type": "Follow", - "id": activity.id, - "object": activity.get_object_id(), - "actor": actor_id, - }, - to=[actor_id], - published=now(), - ) - post_to_outbox(accept) + # Reply to a Follow with an Accept if we're not manully approving them + if not config.MANUALLY_APPROVES_FOLLOWERS: + accept_follow(activity) + else: + update_one_activity( + by_remote_id(activity.id), + upsert({MetaKey.FOLLOW_STATUS: FollowStatus.WAITING.value}), + ) def _update_follow_status(follow_id: str, status: FollowStatus) -> None: diff --git a/core/migrations.py b/core/migrations.py index 606f0f7..29e9758 100644 --- a/core/migrations.py +++ b/core/migrations.py @@ -349,3 +349,15 @@ class _20190906_InReplyToMigration(Migration): ) except Exception: logger.exception(f"failed to process activity {data!r}") + + +class _20191020_ManuallyApprovesFollowerSupportMigrationn(Migration): + def migrate(self) -> None: + DB.activities.update_many( + { + **by_type(ap.ActivityType.FOLLOW), + **in_inbox(), + "meta.follow_status": {"$exists": False}, + }, + {"$set": {"meta.follow_status": "accepted"}}, + ) diff --git a/templates/stream.html b/templates/stream.html index b473a8c..3e74b1b 100644 --- a/templates/stream.html +++ b/templates/stream.html @@ -176,6 +176,15 @@ {% if item | has_type('Follow') %}
new follower + {{ item.meta.follow_status }} + {% if config.MANUALLY_APPROVES_FOLLOWERS and item.meta.follow_status != "accepted" %} +
+ + + + +
+ {% endif %} {% if item.meta.notification_unread %}new{% endif %} {{ (item.activity.published or item.meta.published) | format_timeago }} profile