mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-11-20 08:31:07 +00:00
Send messages
This commit is contained in:
parent
b9d933e3b1
commit
dd554ca6ca
6 changed files with 106 additions and 57 deletions
|
@ -1,9 +1,20 @@
|
|||
''' generates activitypub formatted objects '''
|
||||
from uuid import uuid4
|
||||
from fedireads.settings import DOMAIN
|
||||
from datetime import datetime
|
||||
|
||||
def outbox_collection(user, size):
|
||||
''' outbox okay cool '''
|
||||
return {
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'id': '%s/outbox' % user.actor,
|
||||
'type': 'OrderedCollection',
|
||||
'totalItems': size,
|
||||
'first': '%s/outbox?page=true' % user.actor,
|
||||
'last': '%s/outbox?min_id=0&page=true' % user.actor
|
||||
}
|
||||
|
||||
def shelve_action(user, book, shelf):
|
||||
def shelve_activity(user, book, shelf):
|
||||
''' a user puts a book on a shelf.
|
||||
activitypub action type Add
|
||||
https://www.w3.org/ns/activitystreams#Add '''
|
||||
|
@ -30,6 +41,37 @@ def shelve_action(user, book, shelf):
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
def create_activity(user, obj):
|
||||
''' wraps any object we're broadcasting '''
|
||||
uuid = uuid4()
|
||||
return {
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
|
||||
'id': str(uuid),
|
||||
'type': 'Create',
|
||||
'actor': user.actor,
|
||||
|
||||
'to': ['%s/followers' % user.actor],
|
||||
'cc': ['https://www.w3.org/ns/activitystreams#Public'],
|
||||
|
||||
'object': obj,
|
||||
|
||||
}
|
||||
|
||||
|
||||
def note_object(user, content):
|
||||
''' a lil post '''
|
||||
uuid = uuid4()
|
||||
return {
|
||||
'id': str(uuid),
|
||||
'type': 'Note',
|
||||
'published': datetime.utcnow().isoformat(),
|
||||
'attributedTo': user.actor,
|
||||
'content': content,
|
||||
'to': 'https://www.w3.org/ns/activitystreams#Public'
|
||||
}
|
||||
|
||||
def follow_request(user, follow):
|
||||
''' ask to be friends '''
|
||||
return {
|
||||
|
@ -64,12 +106,11 @@ def actor(user):
|
|||
'id': user.actor,
|
||||
'type': 'Person',
|
||||
'preferredUsername': user.username,
|
||||
'inbox': 'https://%s/api/%s/inbox' % (DOMAIN, user.username),
|
||||
'followers': 'https://%s/api/u/%s/followers' % \
|
||||
(DOMAIN, user.username),
|
||||
'inbox': inbox(user),
|
||||
'followers': '%s/followers' % user.actor,
|
||||
'publicKey': {
|
||||
'id': 'https://%s/api/u/%s#main-key' % (DOMAIN, user.username),
|
||||
'owner': 'https://%s/api/u/%s' % (DOMAIN, user.username),
|
||||
'id': '%s/#main-key' % user.actor,
|
||||
'owner': user.actor,
|
||||
'publicKeyPem': user.public_key,
|
||||
}
|
||||
}
|
||||
|
@ -77,4 +118,4 @@ def actor(user):
|
|||
|
||||
def inbox(user):
|
||||
''' describe an inbox '''
|
||||
return 'https://%s/api/%s/inbox' % (DOMAIN, user.username)
|
||||
return '%s/inbox' % (user.actor)
|
||||
|
|
|
@ -41,7 +41,7 @@ def format_webfinger(user):
|
|||
|
||||
|
||||
@csrf_exempt
|
||||
def actor(request, username):
|
||||
def get_actor(request, username):
|
||||
''' return an activitypub actor object '''
|
||||
user = models.User.objects.get(username=username)
|
||||
return JsonResponse(templates.actor(user))
|
||||
|
@ -52,18 +52,23 @@ def inbox(request, username):
|
|||
''' incoming activitypub events '''
|
||||
if request.method == 'GET':
|
||||
# return a collection of something?
|
||||
pass
|
||||
return JsonResponse({})
|
||||
|
||||
activity = json.loads(request.body)
|
||||
# TODO: RSA key verification
|
||||
|
||||
try:
|
||||
activity = json.loads(request.body)
|
||||
except json.decoder.JSONDecodeError:
|
||||
return HttpResponseBadRequest
|
||||
if activity['type'] == 'Add':
|
||||
handle_add(activity)
|
||||
|
||||
if activity['type'] == 'Follow':
|
||||
response = handle_follow(activity)
|
||||
return JsonResponse(response)
|
||||
|
||||
return HttpResponse()
|
||||
|
||||
|
||||
def handle_add(activity):
|
||||
''' adding a book to a shelf '''
|
||||
book_id = activity['object']['url']
|
||||
|
@ -100,10 +105,12 @@ def handle_follow(activity):
|
|||
|
||||
@csrf_exempt
|
||||
def outbox(request, username):
|
||||
''' outbox for the requested user '''
|
||||
user = models.User.objects.get(username=username)
|
||||
size = models.Message.objects.filter(user=user).count()
|
||||
if request.method == 'GET':
|
||||
# list of activities
|
||||
return JsonResponse()
|
||||
return JsonResponse(templates.outbox_collection(user, size))
|
||||
|
||||
data = request.body.decode('utf-8')
|
||||
if data.activity.type == 'Follow':
|
||||
|
@ -111,42 +118,24 @@ def outbox(request, username):
|
|||
return HttpResponse()
|
||||
|
||||
|
||||
def broadcast_action(sender, action, recipients):
|
||||
def broadcast_activity(sender, obj, recipients):
|
||||
''' sign and send out the actions '''
|
||||
#models.Message(
|
||||
# author=sender,
|
||||
# content=action
|
||||
#).save()
|
||||
activity = templates.create_activity(sender, obj)
|
||||
|
||||
# store message in database
|
||||
models.Message(user=sender, content=activity).save()
|
||||
|
||||
for recipient in recipients:
|
||||
action['to'] = 'https://www.w3.org/ns/activitystreams#Public'
|
||||
action['cc'] = [recipient]
|
||||
broadcast(sender, activity, recipient)
|
||||
|
||||
inbox_fragment = '/api/%s/inbox' % (sender.username)
|
||||
now = datetime.utcnow().isoformat()
|
||||
message_to_sign = '''(request-target): post %s
|
||||
host: https://%s
|
||||
date: %s''' % (inbox_fragment, DOMAIN, now)
|
||||
signer = pkcs1_15.new(RSA.import_key(sender.private_key))
|
||||
signed_message = signer.sign(SHA256.new(message_to_sign.encode('utf8')))
|
||||
|
||||
signature = 'keyId="%s",' % sender.full_username
|
||||
signature += 'headers="(request-target) host date",'
|
||||
signature += 'signature="%s"' % b64encode(signed_message)
|
||||
response = requests.post(
|
||||
recipient,
|
||||
data=json.dumps(action),
|
||||
headers={
|
||||
'Date': now,
|
||||
'Signature': signature,
|
||||
'Host': DOMAIN,
|
||||
},
|
||||
)
|
||||
if not response.ok:
|
||||
return response.raise_for_status()
|
||||
|
||||
def broadcast_follow(sender, action, destination):
|
||||
''' send a follow request '''
|
||||
inbox_fragment = '/api/%s/inbox' % (sender.username)
|
||||
broadcast(sender, action, destination)
|
||||
|
||||
def broadcast(sender, action, destination):
|
||||
''' send out an event to all followers '''
|
||||
inbox_fragment = '/api/u/%s/inbox' % (sender.username)
|
||||
now = datetime.utcnow().isoformat()
|
||||
message_to_sign = '''(request-target): post %s
|
||||
host: https://%s
|
||||
|
@ -167,17 +156,22 @@ date: %s''' % (inbox_fragment, DOMAIN, now)
|
|||
},
|
||||
)
|
||||
if not response.ok:
|
||||
response.raise_for_status()
|
||||
|
||||
response.raise_for_status()
|
||||
|
||||
def get_or_create_remote_user(activity):
|
||||
''' wow, a foreigner '''
|
||||
actor = activity['actor']
|
||||
try:
|
||||
user = models.User.objects.get(actor=actor)
|
||||
except models.User.DoesNotExist:
|
||||
# TODO: how do you actually correctly learn this?
|
||||
username = '%s@%s' % (actor.split('/')[-1], actor.split('/')[2])
|
||||
user = models.User.objects.create_user(username, '', '', actor=actor, local=False)
|
||||
user = models.User.objects.create_user(
|
||||
username,
|
||||
'', '',
|
||||
actor=actor,
|
||||
local=False
|
||||
)
|
||||
return user
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Generated by Django 2.0.13 on 2020-01-27 03:37
|
||||
# Generated by Django 2.0.13 on 2020-01-27 05:42
|
||||
|
||||
from django.conf import settings
|
||||
import django.contrib.auth.models
|
||||
|
@ -76,6 +76,16 @@ class Migration(migrations.Migration):
|
|||
('authors', models.ManyToManyField(to='fedireads.Author')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Message',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('content', django.contrib.postgres.fields.jsonb.JSONField(max_length=5000)),
|
||||
('created_date', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_date', models.DateTimeField(auto_now=True)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Shelf',
|
||||
fields=[
|
||||
|
|
|
@ -64,14 +64,11 @@ def execute_after_save(sender, instance, created, *args, **kwargs):
|
|||
|
||||
class Message(models.Model):
|
||||
''' any kind of user post, incl. reviews, replies, and status updates '''
|
||||
author = models.ForeignKey('User', on_delete=models.PROTECT)
|
||||
user = models.ForeignKey('User', on_delete=models.PROTECT)
|
||||
content = JSONField(max_length=5000)
|
||||
created_date = models.DateTimeField(auto_now_add=True)
|
||||
updated_date = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class Shelf(models.Model):
|
||||
activitypub_id = models.CharField(max_length=255)
|
||||
|
|
|
@ -26,8 +26,8 @@ urlpatterns = [
|
|||
path('shelve/<str:shelf_id>/<int:book_id>', views.shelve),
|
||||
path('follow/', views.follow),
|
||||
path('unfollow/', views.unfollow),
|
||||
path('api/u/<str:username>', federation.actor),
|
||||
path('api/<str:username>/inbox', federation.inbox),
|
||||
path('api/<str:username>/outbox', federation.outbox),
|
||||
path('api/u/<str:username>', federation.get_actor),
|
||||
path('api/u/<str:username>/inbox', federation.inbox),
|
||||
path('api/u/<str:username>/outbox', federation.outbox),
|
||||
path('.well-known/webfinger', federation.webfinger),
|
||||
]
|
||||
|
|
|
@ -7,7 +7,7 @@ from django.template.response import TemplateResponse
|
|||
from django.views.decorators.csrf import csrf_exempt
|
||||
from fedireads import models
|
||||
import fedireads.activitypub_templates as templates
|
||||
from fedireads.federation import broadcast_action, broadcast_follow
|
||||
from fedireads.federation import broadcast_activity, broadcast_follow
|
||||
|
||||
@login_required
|
||||
def home(request):
|
||||
|
@ -73,12 +73,19 @@ def shelve(request, shelf_id, book_id):
|
|||
shelf = models.Shelf.objects.get(identifier=shelf_id)
|
||||
|
||||
# update the database
|
||||
#models.ShelfBook(book=book, shelf=shelf, added_by=request.user).save()
|
||||
models.ShelfBook(book=book, shelf=shelf, added_by=request.user).save()
|
||||
|
||||
# send out the activitypub action
|
||||
action = templates.shelve_action(request.user, book, shelf)
|
||||
summary = '%s marked %s as %s' % (
|
||||
request.user.username,
|
||||
book.data['title'],
|
||||
shelf.name
|
||||
)
|
||||
|
||||
obj = templates.note_object(request.user, summary)
|
||||
#activity = templates.shelve_activity(request.user, book, shelf)
|
||||
recipients = [templates.inbox(u) for u in request.user.followers.all()]
|
||||
broadcast_action(request.user, action, recipients)
|
||||
broadcast_activity(request.user, obj, recipients)
|
||||
|
||||
return redirect('/')
|
||||
|
||||
|
|
Loading…
Reference in a new issue