From 9145dc7a8e057e8dd8958f2131b73a066588f8a0 Mon Sep 17 00:00:00 2001 From: Anthony Wang Date: Wed, 18 Jan 2023 19:48:11 +0000 Subject: [PATCH] Implement some more server features --- README.md | 4 +-- server.py | 72 ++++++++++++++++++++++++++++++++--------------- users/test.jsonld | 2 ++ users/test.liked | 1 + 4 files changed, 54 insertions(+), 25 deletions(-) create mode 100644 users/test.liked diff --git a/README.md b/README.md index 48c032b..1b2e2a0 100644 --- a/README.md +++ b/README.md @@ -37,9 +37,7 @@ Enjoy your new "extremely hardcore" ActivityPub server!!! 🎉😎🚀🙃🥳 Since Fuwuqi's code is super duper easy to read and extend, the following features are left as an exercise to the reader: - Multi-user support (hint: dynamically generate `.well-known/webfinger` instead of serving a static file) -- Liking posts (hint: the implementation is very similar to `Follow`) -- Announcing posts, AKA boosting -- Unfollowing, unliking, etc (hint: implement `collection_remove`) +- Deleting posts - JSON-LD (hint: don't do it, your brain will thank you) - Lots of pain diff --git a/server.py b/server.py index 2c43a46..27f36b0 100644 --- a/server.py +++ b/server.py @@ -15,26 +15,43 @@ domain = 'https://0.exozy.me' def collection_append(file, item): with open(file) as f: collection = load(f) - collection['totalItems'] += 1 collection['orderedItems'].append(item) + collection['totalItems'] += 1 + with open(file, 'w') as f: + dump(collection, f) + + +def collection_pop(file, item): + with open(file) as f: + collection = load(f) + collection['orderedItems'].pop(item) + collection['totalItems'] -= 1 with open(file, 'w') as f: dump(collection, f) def iri_to_actor(iri): if domain in iri: - name = search(f'^{domain}/users/(.*?)#main-key$', iri).group(1) + name = search(f'^{domain}/users/(.*?)$', iri.removesuffix('#main-key')).group(1) actorfile = f'users/{name}' else: - actorfile = f'users/{quote_plus(iri)}' + actorfile = f'users/{quote_plus(iri.removesuffix("#main-key"))}' if not isfile(actorfile): with open(actorfile, 'w') as f: - resp = get(iri.removesuffix('#main-key'), headers={'Accept': 'application/activity+json'}) + resp = get(iri, headers={'Accept': 'application/activity+json'}) f.write(resp.text) with open(actorfile) as f: return load(f) +def send(to, headers, body): + actor = iri_to_actor(to) + headers['Host'] = to.split('/')[2] + resp = post(actor['inbox'], headers=headers, data=body) + print(resp) + print(resp.text) + + class fuwuqi(SimpleHTTPRequestHandler): def do_POST(self): body = self.rfile.read(int(self.headers['Content-Length'])) @@ -72,10 +89,10 @@ class fuwuqi(SimpleHTTPRequestHandler): # Make sure activity doer matches HTTP signature actor = keyid.removesuffix('#main-key') - if 'actor' in activity and activity['actor'] != actor: - self.send_response(401) - return - if 'attributedTo' in activity and activity['attributedTo'] != actor: + if ('actor' in activity and activity['actor'] != actor) or + ('attributedTo' in activity and activity['attributedTo'] != actor) or + ('actor' in activity['object'] and activity['object']['actor'] != actor) or + ('attributedTo' in activity['object'] and activity['object']['attributedTo'] != actor) self.send_response(401) return @@ -85,25 +102,36 @@ class fuwuqi(SimpleHTTPRequestHandler): elif self.path.endswith('outbox'): # C2S collection_append(f'users/{username}.outbox', activity) + # Clients responsible for addressing activity + for to in activity['to']: + if 'followers' in to or to 'https://www.w3.org/ns/activitystreams#Public': + with open(f'users/{username}.followers') as f: + for follower in load(f)['orderedItems']: + send(follower, self.headers, body) + else: + send(to, self.headers, body) + # Process activity if activity['type'] == 'Create': - id = activity['id'].split('/')[-1] + # Post + id = activity['object']['id'].split('/')[-1] with open(f'users/{username}.statuses/{id}', 'w') as f: dump(activity['object'], f) - # Send to followers - with open(f'users/{username}.following') as f: - for followed in load(f)['orderedItems']: - actor = iri_to_actor(followed) - self.headers['Host'] = followed.split('/')[2] - resp = post(actor['inbox'], headers=self.headers, data=body) - print(resp) - print(resp.text) + elif activity['type'] == 'Accept': + # Accept follow request + collection_append(f'users/{username}.followers', activity['object']['actor']) elif activity['type'] == 'Follow': - object = iri_to_actor(activity['object']) - self.headers['Host'] = activity['object'].split('/')[2] - resp = post(object['inbox'], headers=self.headers, data=body) - print(resp) - print(resp.text) + # Follow request collection_append(f'users/{username}.following', activity['object']) + elif activity['type'] == 'Like': + # Like post + collection_append(f'users/{username}.liked', activity['object']) + elif activity['type'] == 'Undo': + if activity['object']['type'] == 'Follow': + # Unfollow request + collection_remove(f'users/{username}.following', activity['object']['object']) + elif activity['object']['type'] == 'Like': + # Unlike post + collection_remove(f'users/{username}.liked', activity['object']['object']) self.send_response(200) self.end_headers() diff --git a/users/test.jsonld b/users/test.jsonld index 82cccc2..a5e58d5 100644 --- a/users/test.jsonld +++ b/users/test.jsonld @@ -7,10 +7,12 @@ "type": "Person", "preferredUsername": "test", "name": "Billiam Wender", + "summary": "idk", "inbox": "https://0.exozy.me/users/test.inbox", "outbox": "https://0.exozy.me/users/test.outbox", "followers": "https://0.exozy.me/users/test.followers", "following": "https://0.exozy.me/users/test.following", + "liked": "https://0.exozy.me/users/test.liked", "icon": { "type": "Image", "mediaType": "image/png", diff --git a/users/test.liked b/users/test.liked new file mode 100644 index 0000000..b465cbd --- /dev/null +++ b/users/test.liked @@ -0,0 +1 @@ +{"@context": "https://www.w3.org/ns/activitystreams", "type": "OrderedCollection", "totalItems": 0, "orderedItems": []}