aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnthony Wang2023-01-18 19:48:11 +0000
committerAnthony Wang2023-01-18 19:48:11 +0000
commit9145dc7a8e057e8dd8958f2131b73a066588f8a0 (patch)
tree654eda145ba1f118fe0198be90345224e3fb703b
parent60bf76a75e105981782c81d6eec693c5990383a9 (diff)
Implement some more server features
-rw-r--r--README.md4
-rw-r--r--server.py72
-rw-r--r--users/test.jsonld2
-rw-r--r--users/test.liked1
4 files changed, 54 insertions, 25 deletions
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": []}