Add support for manually approving followers

Fixes #61
This commit is contained in:
Thomas Sileo 2019-10-20 20:47:35 +02:00
parent e265704e03
commit 95e411ac9b
6 changed files with 73 additions and 19 deletions

View file

@ -31,6 +31,7 @@ from config import JWT
from config import MEDIA_CACHE from config import MEDIA_CACHE
from config import _drop_db from config import _drop_db
from core import feed from core import feed
from core.activitypub import accept_follow
from core.activitypub import activity_url from core.activitypub import activity_url
from core.activitypub import new_context from core.activitypub import new_context
from core.activitypub import post_to_outbox from core.activitypub import post_to_outbox
@ -353,6 +354,24 @@ def api_undo() -> _Response:
return _user_api_response(activity=undo_id) 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"]) @blueprint.route("/new_list", methods=["POST"])
@api_required @api_required
def api_new_list() -> _Response: def api_new_list() -> _Response:

View file

@ -137,6 +137,8 @@ if PROFILE_METADATA:
{"type": "PropertyValue", "name": key, "value": linkify(value)} {"type": "PropertyValue", "name": key, "value": linkify(value)}
) )
MANUALLY_APPROVES_FOLLOWERS = bool(conf.get("manually_approves_followers", False))
ME = { ME = {
"@context": DEFAULT_CTX, "@context": DEFAULT_CTX,
"type": "Person", "type": "Person",
@ -151,7 +153,7 @@ ME = {
"summary": SUMMARY, "summary": SUMMARY,
"endpoints": {}, "endpoints": {},
"url": ID, "url": ID,
"manuallyApprovesFollowers": False, "manuallyApprovesFollowers": MANUALLY_APPROVES_FOLLOWERS,
"attachment": attachments, "attachment": attachments,
"icon": { "icon": {
"mediaType": mimetypes.guess_type(ICON_URL)[0], "mediaType": mimetypes.guess_type(ICON_URL)[0],

View file

@ -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) # Spawn a task to process it (and determine if it needs to be saved)
Tasks.process_reply(create.get_object_id()) 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)

View file

@ -8,9 +8,8 @@ from little_boxes.errors import NotAnActivityError
import config import config
from core.activitypub import _answer_key from core.activitypub import _answer_key
from core.activitypub import accept_follow
from core.activitypub import handle_replies 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.activitypub import update_cached_actor
from core.db import DB from core.db import DB
from core.db import update_one_activity 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 inc
from core.meta import upsert from core.meta import upsert
from core.tasks import Tasks from core.tasks import Tasks
from utils import now
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@ -179,21 +177,14 @@ def _like_process_inbox(like: ap.Like, new_meta: _NewMeta) -> None:
@process_inbox.register @process_inbox.register
def _follow_process_inbox(activity: ap.Follow, new_meta: _NewMeta) -> None: def _follow_process_inbox(activity: ap.Follow, new_meta: _NewMeta) -> None:
_logger.info(f"process_inbox activity={activity!r}") _logger.info(f"process_inbox activity={activity!r}")
# Reply to a Follow with an Accept # Reply to a Follow with an Accept if we're not manully approving them
actor_id = activity.get_actor().id if not config.MANUALLY_APPROVES_FOLLOWERS:
accept = ap.Accept( accept_follow(activity)
actor=config.ID, else:
context=new_context(activity), update_one_activity(
object={ by_remote_id(activity.id),
"type": "Follow", upsert({MetaKey.FOLLOW_STATUS: FollowStatus.WAITING.value}),
"id": activity.id, )
"object": activity.get_object_id(),
"actor": actor_id,
},
to=[actor_id],
published=now(),
)
post_to_outbox(accept)
def _update_follow_status(follow_id: str, status: FollowStatus) -> None: def _update_follow_status(follow_id: str, status: FollowStatus) -> None:

View file

@ -349,3 +349,15 @@ class _20190906_InReplyToMigration(Migration):
) )
except Exception: except Exception:
logger.exception(f"failed to process activity {data!r}") 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"}},
)

View file

@ -176,6 +176,15 @@
{% if item | has_type('Follow') %} {% if item | has_type('Follow') %}
<div style="margin-left:70px;padding-bottom:5px;margin-bottom:15px;display:inline-block;"> <div style="margin-left:70px;padding-bottom:5px;margin-bottom:15px;display:inline-block;">
<span class="bar-item-no-hover">new follower</span> <span class="bar-item-no-hover">new follower</span>
<span class="bar-item-no-hover">{{ item.meta.follow_status }}</span>
{% if config.MANUALLY_APPROVES_FOLLOWERS and item.meta.follow_status != "accepted" %}
<form action="/api/accept_follow" class="action-form" method="POST">
<input type="hidden" name="redirect" value="{{ request.path }}"/>
<input type="hidden" name="id" value="{{ item.remote_id }}"/>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<button type="submit" class="bar-item">approve follow request</button>
</form>
{% endif %}
{% if item.meta.notification_unread %}<span class="bar-item-no-bg"><span class="pcolor">new</span></span>{% endif %} {% if item.meta.notification_unread %}<span class="bar-item-no-bg"><span class="pcolor">new</span></span>{% endif %}
<span class="bar-item-no-bg">{{ (item.activity.published or item.meta.published) | format_timeago }}</span> <span class="bar-item-no-bg">{{ (item.activity.published or item.meta.published) | format_timeago }}</span>
<a class="bar-item" href="/admin/profile?actor_id={{item.meta.actor_id}}">profile</a> <a class="bar-item" href="/admin/profile?actor_id={{item.meta.actor_id}}">profile</a>