mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-11-26 19:41:11 +00:00
commit
857bc6adae
15 changed files with 487 additions and 5 deletions
|
@ -411,6 +411,21 @@ let BookWyrm = new class {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display pop up window.
|
||||
*
|
||||
* @param {string} url Url to open
|
||||
* @param {string} windowName windowName
|
||||
* @return {undefined}
|
||||
*/
|
||||
displayPopUp(url, windowName) {
|
||||
window.open(
|
||||
url,
|
||||
windowName,
|
||||
"left=100,top=100,width=430,height=600"
|
||||
);
|
||||
}
|
||||
|
||||
duplicateInput (event ) {
|
||||
const trigger = event.currentTarget;
|
||||
const input_id = trigger.dataset['duplicate']
|
||||
|
|
|
@ -267,5 +267,6 @@
|
|||
<script src="{% static "js/status_cache.js" %}?v={{ js_cache }}"></script>
|
||||
|
||||
{% block scripts %}{% endblock %}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
70
bookwyrm/templates/ostatus/error.html
Normal file
70
bookwyrm/templates/ostatus/error.html
Normal file
|
@ -0,0 +1,70 @@
|
|||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="block">
|
||||
{% if error == 'invalid_username' %}
|
||||
<div class="notification is-warning has-text-centered" role="status">
|
||||
<p>{% blocktrans %}<strong>{{ account }}</strong> is not a valid username{% endblocktrans %}.</p>
|
||||
<p>{% trans 'Check you have the correct username before trying again' %}.</p>
|
||||
</div>
|
||||
{% elif error == 'user_not_found' %}
|
||||
<div class="notification is-warning has-text-centered" role="status">
|
||||
<p>{% blocktrans %}<strong>{{ account }}</strong> could not be found or <code>{{ remote_domain }}</code> does not support identity discovery{% endblocktrans %}.</p>
|
||||
<p>{% trans 'Check you have the correct username before trying again' %}.</p>
|
||||
</div>
|
||||
{% elif error == 'not_supported' %}
|
||||
<div class="notification is-warning has-text-centered" role="status">
|
||||
<p>{% blocktrans %}<strong>{{ account }}</strong> was found but <code>{{ remote_domain }}</code> does not support 'remote follow'{% endblocktrans %}.</p>
|
||||
<p>{% blocktrans %}Try searching for <strong>{{ user }}</strong> on <code>{{ remote_domain }}</code> instead{% endblocktrans %}.</p>
|
||||
</div>
|
||||
{% elif not request.user.is_authenticated %}
|
||||
<div class="navbar-item">
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<form name="login" method="post" action="{% url 'login' %}?next={{ request.path }}?acct={{ user.remote_id }}">
|
||||
{% csrf_token %}
|
||||
<div class="columns is-variable is-1">
|
||||
<div class="column">
|
||||
<label class="is-sr-only" for="id_localname">{% trans "Username:" %}</label>
|
||||
<input type="text" name="localname" maxlength="150" class="input" required="" id="id_localname" placeholder="{% trans 'username' %}">
|
||||
</div>
|
||||
<div class="column">
|
||||
<label class="is-sr-only" for="id_password">{% trans "Password:" %}</label>
|
||||
<input type="password" name="password" maxlength="128" class="input" required="" id="id_password" placeholder="{% trans 'password' %}">
|
||||
<p class="help"><a href="{% url 'password-reset' %}">{% trans "Forgot your password?" %}</a></p>
|
||||
</div>
|
||||
<div class="column is-narrow">
|
||||
<button class="button is-primary" type="submit">{% trans "Log in" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% elif error == 'ostatus_subscribe' %}
|
||||
<div class="notification is-warning has-text-centered" role="status">
|
||||
<p>{% blocktrans %}Something went wrong trying to follow <strong>{{ account }}</strong>{% endblocktrans %}</p>
|
||||
<p>{% trans 'Check you have the correct username before trying again.' %}</p>
|
||||
</div>
|
||||
{% elif error == 'is_blocked' %}
|
||||
<div class="notification is-danger has-text-centered" role="status">
|
||||
<p>{% blocktrans %}You have blocked <strong>{{ account }}</strong>{% endblocktrans %}</p>
|
||||
</div>
|
||||
{% elif error == 'has_blocked' %}
|
||||
<div class="notification is-danger has-text-centered" role="status">
|
||||
<p>{% blocktrans %}<strong>{{ account }}</strong> has blocked you{% endblocktrans %}</p>
|
||||
</div>
|
||||
{% elif error == 'already_following' %}
|
||||
<div class="notification is-success has-text-centered" role="status">
|
||||
<p>{% blocktrans %}You are already following <strong>{{ account }}</strong>{% endblocktrans %}</p>
|
||||
</div>
|
||||
{% elif error == 'already_requested' %}
|
||||
<div class="notification is-success has-text-centered" role="status">
|
||||
<p>{% blocktrans %}You have already requested to follow <strong>{{ account }}</strong>{% endblocktrans %}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="block is-pulled-right">
|
||||
<button type="button" class="button" onclick="closeWindow()">Close window</button>
|
||||
</div>
|
||||
{% endblock %}
|
46
bookwyrm/templates/ostatus/remote_follow.html
Normal file
46
bookwyrm/templates/ostatus/remote_follow.html
Normal file
|
@ -0,0 +1,46 @@
|
|||
{% extends 'ostatus/template.html' %}
|
||||
{% load i18n %}
|
||||
{% load utilities %}
|
||||
|
||||
{% block heading %}
|
||||
{% blocktrans with username=user.localname sitename=site.name %}Follow {{ username }} on the fediverse{% endblocktrans %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="block card">
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<a href="{{ user.local_path }}" class="media-left">
|
||||
{% include 'snippets/avatar.html' with user=user large=True %}
|
||||
</a>
|
||||
<div class="media-content">
|
||||
<a href="{{ user.local_path }}" class="is-block mb-2">
|
||||
<span class="title is-4 is-block">
|
||||
{{ user.display_name }}
|
||||
{% if user.manually_approves_followers %}
|
||||
<span class="icon icon-lock is-size-7" title="{% trans 'Locked account' %}">
|
||||
<span class="is-sr-only">{% trans "Locked account" %}</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
<span class="subtitle is-7 is-block">@{{ user|username }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="block">
|
||||
<p>{% blocktrans with username=user.display_name %}Follow {{ username }} from another Fediverse account like BookWyrm, Mastodon, or Pleroma.{% endblocktrans %}</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<section class="card-content content">
|
||||
<form name="remote-follow" method="post" action="{% url 'remote-follow' %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="user" value="{{ user.id }}">
|
||||
<label class="label" for="remote_user">{% trans 'User handle to follow from:' %}</label>
|
||||
<input class="input" type="text" name="remote_user" id="remote_user" placeholder="user@example.social" required>
|
||||
<button class="button mt-1 is-primary" type="submit">{% trans 'Follow!' %}</button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
{% endblock %}
|
15
bookwyrm/templates/ostatus/remote_follow_button.html
Normal file
15
bookwyrm/templates/ostatus/remote_follow_button.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
{% load i18n %}
|
||||
{% if request.user == user %}
|
||||
{% else %}
|
||||
|
||||
<div class="field mb-0">
|
||||
<div class="control">
|
||||
<a class="button is-small is-link" href="{% url 'remote-follow-page' %}?user={{ user.username }}" target="_blank" rel="noopener noreferrer" onclick="BookWyrm.displayPopUp(`{% url 'remote-follow-page' %}?user={{ user.username }}`, `remoteFollow`); return false;" aria-describedby="remote_follow_warning">
|
||||
{% blocktrans with username=user.localname %}Follow on Fediverse{% endblocktrans %}
|
||||
</a>
|
||||
</div>
|
||||
<p id="remote_follow_warning" class="mt-1 is-size-7 has-text-weight-light">
|
||||
{% trans 'This link opens in a pop-up window' %}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
63
bookwyrm/templates/ostatus/subscribe.html
Normal file
63
bookwyrm/templates/ostatus/subscribe.html
Normal file
|
@ -0,0 +1,63 @@
|
|||
{% extends 'ostatus/template.html' %}
|
||||
{% load i18n %}
|
||||
{% load utilities %}
|
||||
{% load markdown %}
|
||||
|
||||
{% block title %}
|
||||
{% if not request.user.is_authenticated %}
|
||||
{% blocktrans with sitename=site.name %}Log in to {{ sitename }}{% endblocktrans %}
|
||||
{% elif error %}
|
||||
{% blocktrans with sitename=site.name %}Error following from {{ sitename }}{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans with sitename=site.name %}Follow from {{ sitename }}{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block heading %}
|
||||
{% if error %}
|
||||
{% trans 'Uh oh...' %}
|
||||
{% elif not request.user.is_authenticated %}
|
||||
{% trans "Let's log in first..." %}
|
||||
{% else %}
|
||||
{% blocktrans with sitename=site.name %}Follow from {{ sitename }}{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if error or not request.user.is_authenticated %}
|
||||
{% include 'ostatus/error.html' with error=error user=user account=account %}
|
||||
{% else %}
|
||||
<div class="block card">
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<a href="{{ user.local_path }}" class="media-left">
|
||||
{% include 'snippets/avatar.html' with user=user large=True %}
|
||||
</a>
|
||||
<div class="media-content">
|
||||
<a href="{{ user.local_path }}" class="is-block mb-2">
|
||||
<span class="title is-4 is-block">
|
||||
{{ user.display_name }}
|
||||
{% if user.manually_approves_followers %}
|
||||
<span class="icon icon-lock is-size-7" title="{% trans 'Locked account' %}">
|
||||
<span class="is-sr-only">{% trans 'Locked account' %}</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
<span class="subtitle is-7 is-block">@{{ user|username }}</span>
|
||||
</a>
|
||||
<form name="follow" method="post" action="{% url 'follow' %}/?next={% url 'ostatus-success' %}?following={{ user.username }}">
|
||||
{% csrf_token %}
|
||||
<input name="user" value="{{ user.username }}" hidden>
|
||||
<button class="button is-link" type="submit">{% blocktrans with username=user.display_name %}Follow {{ username }}{% endblocktrans %}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{% if user.summary %}
|
||||
{{ user.summary|to_markdown|safe|truncatechars_html:120 }}
|
||||
{% else %} {% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
35
bookwyrm/templates/ostatus/success.html
Normal file
35
bookwyrm/templates/ostatus/success.html
Normal file
|
@ -0,0 +1,35 @@
|
|||
{% extends 'ostatus/template.html' %}
|
||||
{% load i18n %}
|
||||
{% load utilities %}
|
||||
|
||||
{% block content %}
|
||||
<div class="block card">
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<a href="{{ user.local_path }}" class="media-left">
|
||||
{% include 'snippets/avatar.html' with user=user large=True %}
|
||||
</a>
|
||||
<div class="media-content">
|
||||
<a href="{{ user.local_path }}" class="is-block mb-2">
|
||||
<span class="title is-4 is-block">
|
||||
{{ user.display_name }}
|
||||
{% if user.manually_approves_followers %}
|
||||
<span class="icon icon-lock is-size-7" title="{% trans 'Locked account' %}">
|
||||
<span class="is-sr-only">{% trans "Locked account" %}</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
<span class="subtitle is-7 is-block">@{{ user|username }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<p class="notification is-success">
|
||||
<span class="icon icon-check m-0-mobile" aria-hidden="true"></span>
|
||||
<span>{% blocktrans with display_name=user.display_name %}You are now following {{ display_name }}!{% endblocktrans %}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="block is-pulled-right">
|
||||
<button type="button" class="button" onclick="closeWindow()">Close window</button>
|
||||
</div>
|
||||
{% endblock %}
|
41
bookwyrm/templates/ostatus/template.html
Normal file
41
bookwyrm/templates/ostatus/template.html
Normal file
|
@ -0,0 +1,41 @@
|
|||
{% load layout %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load utilities %}
|
||||
{% load markdown %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="{% get_lang %}">
|
||||
<head>
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="{% static 'css/vendor/bulma.min.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/vendor/icons.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/bookwyrm.css' %}">
|
||||
<script>
|
||||
function closeWindow() {
|
||||
window.close();
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar" aria-label="main navigation">
|
||||
<div class="container">
|
||||
<div class="navbar-brand">
|
||||
<img class="image logo navbar-item" src="{% if site.logo_small %}{% get_media_prefix %}{{ site.logo_small }}{% else %}{% static 'images/logo-small.png' %}{% endif %}" alt="Home page">
|
||||
<h2 class="navbar-item subtitle">{% block heading %}{% endblock %}</h2>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="section is-flex-grow-1 columns is-centered">
|
||||
<div class="block column is-one-third">
|
||||
{% block content%}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var csrf_token = '{{ csrf_token }}';
|
||||
</script>
|
||||
<script src="{% static 'js/bookwyrm.js' %}?v={{ js_cache }}"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -39,6 +39,9 @@
|
|||
{% if not is_self and request.user.is_authenticated %}
|
||||
{% include 'snippets/follow_button.html' with user=user %}
|
||||
{% endif %}
|
||||
{% if not is_self %}
|
||||
{% include 'ostatus/remote_follow_button.html' with user=user %}
|
||||
{% endif %}
|
||||
|
||||
{% if is_self and user.follower_requests.all %}
|
||||
<div class="follow-requests">
|
||||
|
|
|
@ -4,10 +4,12 @@ from unittest.mock import patch
|
|||
|
||||
from django.contrib.auth.models import Group, Permission
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.template.response import TemplateResponse
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
from bookwyrm import models, views
|
||||
from bookwyrm.tests.validate_html import validate_html
|
||||
|
||||
|
||||
@patch("bookwyrm.activitystreams.add_user_statuses_task.delay")
|
||||
|
@ -16,6 +18,7 @@ class FollowViews(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
"""we need basic test data and mocks"""
|
||||
models.SiteSettings.objects.create()
|
||||
self.factory = RequestFactory()
|
||||
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch(
|
||||
"bookwyrm.activitystreams.populate_stream_task.delay"
|
||||
|
@ -174,3 +177,43 @@ class FollowViews(TestCase):
|
|||
self.assertEqual(models.UserFollowRequest.objects.filter(id=rel.id).count(), 0)
|
||||
# follow relationship should not exist
|
||||
self.assertEqual(models.UserFollows.objects.filter(id=rel.id).count(), 0)
|
||||
|
||||
def test_ostatus_follow_request(self, _):
|
||||
"""check ostatus subscribe template loads"""
|
||||
request = self.factory.get(
|
||||
"", {"acct": "https%3A%2F%2Fexample.com%2Fusers%2Frat"}
|
||||
)
|
||||
request.user = self.local_user
|
||||
result = views.ostatus_follow_request(request)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_remote_follow_page(self, _):
|
||||
"""check remote follow page loads"""
|
||||
request = self.factory.get("", {"acct": "mouse@local.com"})
|
||||
request.user = self.remote_user
|
||||
result = views.remote_follow_page(request)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_ostatus_follow_success(self, _):
|
||||
"""check remote follow success page loads"""
|
||||
request = self.factory.get("")
|
||||
request.user = self.remote_user
|
||||
request.following = "mouse@local.com"
|
||||
result = views.ostatus_follow_success(request)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_remote_follow(self, _):
|
||||
"""check follow from remote page loads"""
|
||||
request = self.factory.post("", {"user": self.remote_user.id})
|
||||
request.user = self.remote_user
|
||||
request.remote_user = "mouse@local.com"
|
||||
result = views.remote_follow(request)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
|
|
@ -44,6 +44,7 @@ urlpatterns = [
|
|||
re_path(r"^api/v1/instance/?$", views.instance_info),
|
||||
re_path(r"^api/v1/instance/peers/?$", views.peers),
|
||||
re_path(r"^opensearch.xml$", views.opensearch, name="opensearch"),
|
||||
re_path(r"^ostatus_subscribe/?$", views.ostatus_follow_request),
|
||||
# polling updates
|
||||
re_path("^api/updates/notifications/?$", views.get_notification_count),
|
||||
re_path("^api/updates/stream/(?P<stream>[a-z]+)/?$", views.get_unread_status_count),
|
||||
|
@ -450,4 +451,9 @@ urlpatterns = [
|
|||
re_path(r"^unfollow/?$", views.unfollow, name="unfollow"),
|
||||
re_path(r"^accept-follow-request/?$", views.accept_follow_request),
|
||||
re_path(r"^delete-follow-request/?$", views.delete_follow_request),
|
||||
re_path(r"^ostatus_follow/?$", views.remote_follow, name="remote-follow"),
|
||||
re_path(r"^remote_follow/?$", views.remote_follow_page, name="remote-follow-page"),
|
||||
re_path(
|
||||
r"^ostatus_success/?$", views.ostatus_follow_success, name="ostatus-success"
|
||||
),
|
||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
|
|
@ -58,7 +58,14 @@ from .author import Author, EditAuthor
|
|||
from .directory import Directory
|
||||
from .discover import Discover
|
||||
from .feed import DirectMessage, Feed, Replies, Status
|
||||
from .follow import follow, unfollow
|
||||
from .follow import (
|
||||
follow,
|
||||
unfollow,
|
||||
ostatus_follow_request,
|
||||
ostatus_follow_success,
|
||||
remote_follow,
|
||||
remote_follow_page,
|
||||
)
|
||||
from .follow import accept_follow_request, delete_follow_request
|
||||
from .get_started import GetStartedBooks, GetStartedProfile, GetStartedUsers
|
||||
from .goal import Goal, hide_goal
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
""" views for actions you can take in the application """
|
||||
import urllib.parse
|
||||
import re
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.db import IntegrityError
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.template.response import TemplateResponse
|
||||
from django.views.decorators.http import require_POST
|
||||
|
||||
from bookwyrm import models
|
||||
from .helpers import get_user_from_username
|
||||
from .helpers import (
|
||||
get_user_from_username,
|
||||
handle_remote_webfinger,
|
||||
subscribe_remote_webfinger,
|
||||
WebFingerError,
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -23,6 +31,9 @@ def follow(request):
|
|||
except IntegrityError:
|
||||
pass
|
||||
|
||||
if request.GET.get("next"):
|
||||
return redirect(request.GET.get("next", "/"))
|
||||
|
||||
return redirect(to_follow.local_path)
|
||||
|
||||
|
||||
|
@ -84,3 +95,91 @@ def delete_follow_request(request):
|
|||
|
||||
follow_request.delete()
|
||||
return redirect(f"/user/{request.user.localname}")
|
||||
|
||||
|
||||
def ostatus_follow_request(request):
|
||||
"""prepare an outgoing remote follow request"""
|
||||
uri = urllib.parse.unquote(request.GET.get("acct"))
|
||||
username_parts = re.search(
|
||||
r"(?:^http(?:s?):\/\/)([\w\-\.]*)(?:.)*(?:(?:\/)([\w]*))", uri
|
||||
)
|
||||
account = f"{username_parts[2]}@{username_parts[1]}"
|
||||
user = handle_remote_webfinger(account)
|
||||
error = None
|
||||
|
||||
if user is None or user == "":
|
||||
error = "ostatus_subscribe"
|
||||
|
||||
# don't do these checks for AnonymousUser before they sign in
|
||||
if request.user.is_authenticated:
|
||||
|
||||
# you have blocked them so you probably don't want to follow
|
||||
if hasattr(request.user, "blocks") and user in request.user.blocks.all():
|
||||
error = "is_blocked"
|
||||
# they have blocked you
|
||||
if hasattr(user, "blocks") and request.user in user.blocks.all():
|
||||
error = "has_blocked"
|
||||
# you're already following them
|
||||
if hasattr(user, "followers") and request.user in user.followers.all():
|
||||
error = "already_following"
|
||||
# you're not following yet but you already asked
|
||||
if (
|
||||
hasattr(user, "follower_requests")
|
||||
and request.user in user.follower_requests.all()
|
||||
):
|
||||
error = "already_requested"
|
||||
|
||||
data = {"account": account, "user": user, "error": error}
|
||||
|
||||
return TemplateResponse(request, "ostatus/subscribe.html", data)
|
||||
|
||||
|
||||
@login_required
|
||||
def ostatus_follow_success(request):
|
||||
"""display success message for remote follow"""
|
||||
user = get_user_from_username(request.user, request.GET.get("following"))
|
||||
data = {"account": user.name, "user": user, "error": None}
|
||||
return TemplateResponse(request, "ostatus/success.html", data)
|
||||
|
||||
|
||||
def remote_follow_page(request):
|
||||
"""display remote follow page"""
|
||||
user = get_user_from_username(request.user, request.GET.get("user"))
|
||||
data = {"user": user}
|
||||
return TemplateResponse(request, "ostatus/remote_follow.html", data)
|
||||
|
||||
|
||||
@require_POST
|
||||
def remote_follow(request):
|
||||
"""direct user to follow from remote account using ostatus subscribe protocol"""
|
||||
remote_user = request.POST.get("remote_user")
|
||||
try:
|
||||
if remote_user[0] == "@":
|
||||
remote_user = remote_user[1:]
|
||||
remote_domain = remote_user.split("@")[1]
|
||||
except (TypeError, IndexError):
|
||||
remote_domain = None
|
||||
|
||||
wf_response = subscribe_remote_webfinger(remote_user)
|
||||
user = get_object_or_404(models.User, id=request.POST.get("user"))
|
||||
|
||||
if wf_response is None:
|
||||
data = {
|
||||
"account": remote_user,
|
||||
"user": user,
|
||||
"error": "not_supported",
|
||||
"remote_domain": remote_domain,
|
||||
}
|
||||
return TemplateResponse(request, "ostatus/subscribe.html", data)
|
||||
|
||||
if isinstance(wf_response, WebFingerError):
|
||||
data = {
|
||||
"account": remote_user,
|
||||
"user": user,
|
||||
"error": str(wf_response),
|
||||
"remote_domain": remote_domain,
|
||||
}
|
||||
return TemplateResponse(request, "ostatus/subscribe.html", data)
|
||||
|
||||
url = wf_response.replace("{uri}", urllib.parse.quote(user.remote_id))
|
||||
return redirect(url)
|
||||
|
|
|
@ -16,6 +16,13 @@ from bookwyrm.status import create_generated_note
|
|||
from bookwyrm.utils import regex
|
||||
|
||||
|
||||
# pylint: disable=unnecessary-pass
|
||||
class WebFingerError(Exception):
|
||||
"""empty error class for problems finding user information with webfinger"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def get_user_from_username(viewer, username):
|
||||
"""helper function to resolve a localname or a username to a user"""
|
||||
if viewer.is_authenticated and viewer.localname == username:
|
||||
|
@ -57,10 +64,8 @@ def handle_remote_webfinger(query):
|
|||
# usernames could be @user@domain or user@domain
|
||||
if not query:
|
||||
return None
|
||||
|
||||
if query[0] == "@":
|
||||
query = query[1:]
|
||||
|
||||
try:
|
||||
domain = query.split("@")[1]
|
||||
except IndexError:
|
||||
|
@ -86,6 +91,35 @@ def handle_remote_webfinger(query):
|
|||
return user
|
||||
|
||||
|
||||
def subscribe_remote_webfinger(query):
|
||||
"""get subscribe template from other servers"""
|
||||
template = None
|
||||
# usernames could be @user@domain or user@domain
|
||||
if not query:
|
||||
return WebFingerError("invalid_username")
|
||||
|
||||
if query[0] == "@":
|
||||
query = query[1:]
|
||||
|
||||
try:
|
||||
domain = query.split("@")[1]
|
||||
except IndexError:
|
||||
return WebFingerError("invalid_username")
|
||||
|
||||
url = f"https://{domain}/.well-known/webfinger?resource=acct:{query}"
|
||||
|
||||
try:
|
||||
data = get_data(url)
|
||||
except (ConnectorException, HTTPError):
|
||||
return WebFingerError("user_not_found")
|
||||
|
||||
for link in data.get("links"):
|
||||
if link.get("rel") == "http://ostatus.org/schema/1.0/subscribe":
|
||||
template = link["template"]
|
||||
|
||||
return template
|
||||
|
||||
|
||||
def get_edition(book_id):
|
||||
"""look up a book in the db and return an edition"""
|
||||
book = models.Book.objects.select_subclasses().get(id=book_id)
|
||||
|
|
|
@ -30,7 +30,11 @@ def webfinger(request):
|
|||
"rel": "self",
|
||||
"type": "application/activity+json",
|
||||
"href": user.remote_id,
|
||||
}
|
||||
},
|
||||
{
|
||||
"rel": "http://ostatus.org/schema/1.0/subscribe",
|
||||
"template": f"https://{DOMAIN}/ostatus_subscribe?acct={{uri}}",
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue