forked from mirrors/bookwyrm
commit
44168e74ad
11 changed files with 114 additions and 24 deletions
|
@ -3,8 +3,9 @@ import inspect
|
|||
import sys
|
||||
|
||||
from .base_activity import ActivityEncoder, Image, PublicKey, Signature
|
||||
from .base_activity import Link, Mention
|
||||
from .note import Note, GeneratedNote, Article, Comment, Review, Quotation
|
||||
from .note import Tombstone, Link
|
||||
from .note import Tombstone
|
||||
from .interaction import Boost, Like
|
||||
from .ordered_collection import OrderedCollection, OrderedCollectionPage
|
||||
from .person import Person
|
||||
|
|
|
@ -21,6 +21,19 @@ class Image:
|
|||
type: str = 'Image'
|
||||
|
||||
|
||||
@dataclass
|
||||
class Link():
|
||||
''' for tagging a book in a status '''
|
||||
href: str
|
||||
name: str
|
||||
type: str = 'Link'
|
||||
|
||||
@dataclass
|
||||
class Mention(Link):
|
||||
''' a subtype of Link for mentioning an actor '''
|
||||
type: str = 'Mention'
|
||||
|
||||
|
||||
@dataclass
|
||||
class PublicKey:
|
||||
''' public key block '''
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
from dataclasses import dataclass, field
|
||||
from typing import Dict, List
|
||||
|
||||
from .base_activity import ActivityObject, Image
|
||||
from .base_activity import ActivityObject, Image, Link
|
||||
|
||||
@dataclass(init=False)
|
||||
class Tombstone(ActivityObject):
|
||||
|
@ -20,6 +20,7 @@ class Note(ActivityObject):
|
|||
inReplyTo: str
|
||||
published: str
|
||||
attributedTo: str
|
||||
tag: List[Link]
|
||||
to: List[str]
|
||||
cc: List[str]
|
||||
content: str
|
||||
|
@ -36,17 +37,9 @@ class Article(Note):
|
|||
type: str = 'Article'
|
||||
|
||||
|
||||
@dataclass
|
||||
class Link():
|
||||
''' for tagging a book in a status '''
|
||||
href: str
|
||||
name: str
|
||||
type: str = 'Link'
|
||||
|
||||
@dataclass(init=False)
|
||||
class GeneratedNote(Note):
|
||||
''' just a re-typed note '''
|
||||
tag: List[Link]
|
||||
type: str = 'GeneratedNote'
|
||||
|
||||
|
||||
|
|
26
bookwyrm/migrations/0063_auto_20201101_1758.py
Normal file
26
bookwyrm/migrations/0063_auto_20201101_1758.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
# Generated by Django 3.0.7 on 2020-11-01 17:58
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bookwyrm', '0062_auto_20201031_1936'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveConstraint(
|
||||
model_name='notification',
|
||||
name='notification_type_valid',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='notification',
|
||||
name='notification_type',
|
||||
field=models.CharField(choices=[('FAVORITE', 'Favorite'), ('REPLY', 'Reply'), ('MENTION', 'Mention'), ('TAG', 'Tag'), ('FOLLOW', 'Follow'), ('FOLLOW_REQUEST', 'Follow Request'), ('BOOST', 'Boost'), ('IMPORT', 'Import')], max_length=255),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='notification',
|
||||
constraint=models.CheckConstraint(check=models.Q(notification_type__in=['FAVORITE', 'REPLY', 'MENTION', 'TAG', 'FOLLOW', 'FOLLOW_REQUEST', 'BOOST', 'IMPORT']), name='notification_type_valid'),
|
||||
),
|
||||
]
|
|
@ -59,7 +59,8 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
|
|||
|
||||
@property
|
||||
def ap_tag(self):
|
||||
''' books or (eventually) users tagged in a post '''
|
||||
''' references to books and/or users '''
|
||||
|
||||
tags = []
|
||||
for book in self.mention_books.all():
|
||||
tags.append(activitypub.Link(
|
||||
|
@ -67,6 +68,11 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
|
|||
name=book.title,
|
||||
type='Book'
|
||||
))
|
||||
for user in self.mention_users.all():
|
||||
tags.append(activitypub.Mention(
|
||||
href=user.remote_id,
|
||||
name=user.username,
|
||||
))
|
||||
return tags
|
||||
|
||||
shared_mappings = [
|
||||
|
@ -286,7 +292,7 @@ class ReadThrough(BookWyrmModel):
|
|||
|
||||
NotificationType = models.TextChoices(
|
||||
'NotificationType',
|
||||
'FAVORITE REPLY TAG FOLLOW FOLLOW_REQUEST BOOST IMPORT')
|
||||
'FAVORITE REPLY MENTION TAG FOLLOW FOLLOW_REQUEST BOOST IMPORT')
|
||||
|
||||
class Notification(BookWyrmModel):
|
||||
''' you've been tagged, liked, followed, etc '''
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
''' handles all the activity coming out of the server '''
|
||||
from datetime import datetime
|
||||
import re
|
||||
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.http import HttpResponseNotFound, JsonResponse
|
||||
|
@ -13,6 +14,8 @@ from bookwyrm.status import create_tag, create_notification
|
|||
from bookwyrm.status import create_generated_note
|
||||
from bookwyrm.status import delete_status
|
||||
from bookwyrm.remote_user import get_or_create_remote_user
|
||||
from bookwyrm.settings import DOMAIN
|
||||
from bookwyrm.utils import regex
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
|
@ -34,13 +37,17 @@ def outbox(request, username):
|
|||
|
||||
|
||||
def handle_remote_webfinger(query):
|
||||
''' webfingerin' other servers '''
|
||||
''' webfingerin' other servers, username query should be user@domain '''
|
||||
user = None
|
||||
domain = query.split('@')[1]
|
||||
try:
|
||||
domain = query.split('@')[2]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
try:
|
||||
user = models.User.objects.get(username=query)
|
||||
except models.User.DoesNotExist:
|
||||
url = 'https://%s/.well-known/webfinger?resource=acct:%s' % \
|
||||
url = 'https://%s/.well-known/webfinger?resource=acct:@%s' % \
|
||||
(domain, query)
|
||||
try:
|
||||
response = requests.get(url)
|
||||
|
@ -55,7 +62,7 @@ def handle_remote_webfinger(query):
|
|||
user = get_or_create_remote_user(link['href'])
|
||||
except KeyError:
|
||||
return None
|
||||
return [user]
|
||||
return user
|
||||
|
||||
|
||||
def handle_follow(user, to_follow):
|
||||
|
@ -211,7 +218,36 @@ def handle_status(user, form):
|
|||
''' generic handler for statuses '''
|
||||
status = form.save()
|
||||
|
||||
# notify reply parent or (TODO) tagged users
|
||||
# inspect the text for user tags
|
||||
text = status.content
|
||||
matches = re.finditer(
|
||||
regex.username,
|
||||
text
|
||||
)
|
||||
for match in matches:
|
||||
username = match.group().strip().split('@')[1:]
|
||||
if len(username) == 1:
|
||||
# this looks like a local user (@user), fill in the domain
|
||||
username.append(DOMAIN)
|
||||
username = '@'.join(username)
|
||||
|
||||
mention_user = handle_remote_webfinger(username)
|
||||
if not mention_user:
|
||||
# we can ignore users we don't know about
|
||||
continue
|
||||
# add them to status mentions fk
|
||||
status.mention_users.add(mention_user)
|
||||
# create notification if the mentioned user is local
|
||||
if mention_user.local:
|
||||
create_notification(
|
||||
mention_user,
|
||||
'MENTION',
|
||||
related_user=user,
|
||||
related_status=status
|
||||
)
|
||||
status.save()
|
||||
|
||||
# notify reply parent or tagged users
|
||||
if status.reply_parent and status.reply_parent.user.local:
|
||||
create_notification(
|
||||
status.reply_parent.user,
|
||||
|
|
|
@ -22,6 +22,10 @@
|
|||
favorited your
|
||||
<a href="{{ notification.related_status.remote_id}}">status</a>
|
||||
|
||||
{% elif notification.notification_type == 'MENTION' %}
|
||||
mentioned you in a
|
||||
<a href="{{ notification.related_status.remote_id}}">status</a>
|
||||
|
||||
{% elif notification.notification_type == 'REPLY' %}
|
||||
<a href="{{ notification.related_status.remote_id}}">replied</a>
|
||||
to your
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
from .regex import username
|
5
bookwyrm/utils/regex.py
Normal file
5
bookwyrm/utils/regex.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
''' defining regexes for regularly used concepts '''
|
||||
|
||||
domain = r'[a-z-A-Z0-9_\-]+\.[a-z]+'
|
||||
username = r'@[a-zA-Z_\-\.0-9]+(@%s)?' % domain
|
||||
full_username = r'@[a-zA-Z_\-\.0-9]+@%s' % domain
|
|
@ -16,6 +16,7 @@ from bookwyrm.activitypub import ActivityEncoder
|
|||
from bookwyrm import forms, models, books_manager
|
||||
from bookwyrm import goodreads_import
|
||||
from bookwyrm.tasks import app
|
||||
from bookwyrm.utils import regex
|
||||
|
||||
|
||||
def get_user_from_username(username):
|
||||
|
@ -168,7 +169,7 @@ def search(request):
|
|||
return JsonResponse([r.__dict__ for r in book_results], safe=False)
|
||||
|
||||
# use webfinger looks like a mastodon style account@domain.com username
|
||||
if re.match(r'\w+@\w+.\w+', query):
|
||||
if re.match(regex.full_username, query):
|
||||
outgoing.handle_remote_webfinger(query)
|
||||
|
||||
# do a local user search
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
''' responds to various requests to /.well-know '''
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from django.http import HttpResponseBadRequest, HttpResponseNotFound
|
||||
from django.http import HttpResponseNotFound
|
||||
from django.http import JsonResponse
|
||||
|
||||
from bookwyrm import models
|
||||
|
@ -16,13 +17,16 @@ def webfinger(request):
|
|||
|
||||
resource = request.GET.get('resource')
|
||||
if not resource and not resource.startswith('acct:'):
|
||||
return HttpResponseBadRequest()
|
||||
ap_id = resource.replace('acct:', '')
|
||||
user = models.User.objects.filter(username=ap_id).first()
|
||||
if not user:
|
||||
return HttpResponseNotFound()
|
||||
|
||||
username = resource.replace('acct:@', '')
|
||||
try:
|
||||
user = models.User.objects.get(username=username)
|
||||
except models.User.DoesNotExist:
|
||||
return HttpResponseNotFound('No account found')
|
||||
|
||||
return JsonResponse({
|
||||
'subject': 'acct:%s' % (user.username),
|
||||
'subject': 'acct:@%s' % (user.username),
|
||||
'links': [
|
||||
{
|
||||
'rel': 'self',
|
||||
|
|
Loading…
Reference in a new issue