Send messages

This commit is contained in:
Mouse Reeve 2020-01-26 20:57:48 -08:00
parent b9d933e3b1
commit dd554ca6ca
6 changed files with 106 additions and 57 deletions

View file

@ -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)

View file

@ -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

View file

@ -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=[

View file

@ -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)

View file

@ -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),
]

View file

@ -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('/')