From 53f0c72117894c389e6e4c2990f802486eff330e Mon Sep 17 00:00:00 2001
From: Anthony Wang
Date: Wed, 18 Jan 2023 05:01:59 +0000
Subject: Implement (broken) HTTP signatures
---
README.md | 2 +-
activity.jsonld | 14 ++++
client.py | 35 ++++++++
main.py | 10 ---
private.pem | 28 +++++++
public.pem | 9 ++
server.py | 87 +++++++++++++++++++
...F%2F0.exozy.me%2Fusers%2Ftest.jsonld%23main-key | 24 ++++++
...%3A%2F%2Fsocial.exozy.me%2Fusers%2Fa%23main-key | 98 ++++++++++++++++++++++
users/test.followers | 6 ++
users/test.following | 6 ++
users/test.inbox | 6 ++
users/test.jsonld | 2 +
users/test.outbox | 1 +
users/test.statuses/hello-world | 1 +
users/test.statuses/hello-world2 | 1 +
16 files changed, 319 insertions(+), 11 deletions(-)
create mode 100644 activity.jsonld
create mode 100644 client.py
delete mode 100644 main.py
create mode 100644 private.pem
create mode 100644 public.pem
create mode 100644 server.py
create mode 100644 users/https%3A%2F%2F0.exozy.me%2Fusers%2Ftest.jsonld%23main-key
create mode 100644 users/https%3A%2F%2Fsocial.exozy.me%2Fusers%2Fa%23main-key
create mode 100644 users/test.followers
create mode 100644 users/test.following
create mode 100644 users/test.inbox
create mode 100644 users/test.outbox
create mode 100644 users/test.statuses/hello-world
create mode 100644 users/test.statuses/hello-world2
diff --git a/README.md b/README.md
index 06c228d..b70d965 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,7 @@ That wasn't so bad, was it? (sobbing sounds)
Alright, time for the real deal. Start up `python main.py` on your server. If you want to customize the port or whatever, just change the source code.
-Now on your client device, open up your favorite C2S ActivityPub client... oh wait... there aren't any! Welp, you'll just have to settle for reading the [AP spec](https://www.w3.org/TR/activitypub/) and `curl`ing some homemade JSON. That's rough, buddy.
+Now on your client device, open up your favorite C2S ActivityPub client... oh wait... there aren't any! Welp, you'll just have to settle for reading the [AP spec](https://www.w3.org/TR/activitypub/), writing some homemade JSON, and figuring out how `python client.py` works.
Like and subscribe and enjoy your new "extremely hardcore" ActivityPub server!!! 🎉😎🚀🙃🥳
diff --git a/activity.jsonld b/activity.jsonld
new file mode 100644
index 0000000..ab5837d
--- /dev/null
+++ b/activity.jsonld
@@ -0,0 +1,14 @@
+{
+ "@context": "https://www.w3.org/ns/activitystreams",
+ "id": "https://0.exozy.me/users/test.outbox/hello-world2",
+ "type": "Create",
+ "actor": "https://0.exozy.me/users/test.jsonld",
+ "object": {
+ "id": "https://0.exozy.me/users/test.statuses/hello-world2",
+ "type": "Note",
+ "attributedTo": "https://0.exozy.me/users/test.jsonld",
+ "inReplyTo": "https://social.exozy.me/@a/109707513227348721",
+ "content": "Hello from fuwuqi! 2",
+ "to": "https://www.w3.org/ns/activitystreams#Public"
+ }
+}
diff --git a/client.py b/client.py
new file mode 100644
index 0000000..8cf4281
--- /dev/null
+++ b/client.py
@@ -0,0 +1,35 @@
+from cryptography.hazmat.primitives import hashes, serialization
+from cryptography.hazmat.primitives.asymmetric import padding
+from base64 import b64encode
+from email.utils import formatdate
+from requests import post
+
+date = formatdate(usegmt=True)
+
+with open('activity.jsonld', 'rb') as f:
+ activity = f.read()
+
+digester = hashes.Hash(hashes.SHA256())
+digester.update(activity)
+digest = b64encode(digester.finalize()).decode()
+message = f'(request-target): post /users/a/inbox\nhost: social.exozy.me\ndate: {date}\ndigest: SHA-256={digest}'
+
+with open('private.pem', 'rb') as f:
+ privkey = serialization.load_pem_private_key(f.read(), None)
+
+signature = b64encode(privkey.sign(
+ message.encode('utf8'),
+ padding.PKCS1v15(),
+ hashes.SHA256()
+)).decode()
+header = f'keyId="https://0.exozy.me/users/test.jsonld#main-key",headers="(request-target) host date digest",signature="{signature}"'
+
+resp = post('http://localhost:4200/users/test.outbox', headers={
+ '(request-target)': 'post /users/a/inbox',
+ 'Host': 'social.exozy.me',
+ 'Date': date,
+ 'Digest': f'SHA-256={digest}',
+ 'Signature': header,
+}, data=activity)
+print(resp)
+print(resp.text)
diff --git a/main.py b/main.py
deleted file mode 100644
index 5ef7f9b..0000000
--- a/main.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from http.server import SimpleHTTPRequestHandler, HTTPServer
-
-class fuwuqi(SimpleHTTPRequestHandler):
- def do_POST(self):
- self.send_response(200)
- self.send_header('Content-type', 'text/html')
- self.end_headers()
- self.wfile.write('hi!'.encode('utf-8'))
-
-HTTPServer(('localhost', 4200), fuwuqi).serve_forever()
diff --git a/private.pem b/private.pem
new file mode 100644
index 0000000..93c0375
--- /dev/null
+++ b/private.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCc+fpJFDUlPCoU
+wUmrsGDExb/hX8dhKMc9u0ov/ePBc5OY4tTPjCvghvUjCIqmyC7PS7JfUuNt7epD
+OpoJm7mF2gaj64mxqfPrICgtjARN8gUiOpCvcWggZDFgkMQuSFI7yG1s4oqhGJUq
+yMPnHhBwSRmQsBp5/xZWHTywSoTd8Li4uY6VJq/JxswuZqH1R2GWMVxpVJDuqjH0
+cW8oXZ6LgGY3ZZ0wZnwk3bSdzQ7GhmkGkwCtGoXSSuMiSEDn/0K8hkRQs9AKFgvo
+ByYtLSP/JPETzZ4UwEG/J6DwgIxHNxsKg36FWe/dxv/v4HA12hCNmq3REb+GFulz
+xEq35g71AgMBAAECggEACqnGcS67ZMyfSn/4xP4QYhLRc89MSBLQ5KYeJirCd70l
+97riMY5i2qKARhbJ8wYNJqK3OqzIQIrqoLzQguTo/NPbI0kYNjvbvbXrqhsP8yAv
+bhb5W27s36ppWkHAjyj1gRRz2R4obu9bDqggT/Olso2cpsyiTSA2qwyFt1n7MueQ
+ag/lSYAAnjRfnxnGiBwy+wURxZH+Y9uBhb9E8MZ+W+sUc7l8RDUfYPN9d7NmxfdV
+x6x+jk+OLpYotj1v6aTfdwBCqpdt2ulMZexTCfjwAIU/XI2uhqaO1dzKI+Ssr/WY
+j8R7LXV94pI1ZXdwAJ/8aWGllAhj1hpnhacjnr7JAQKBgQDZPj5zJVk7TBoRxQVw
+VCfmGwPwAkfmGGqn9g41VlKtyG+JhWQ7aAs0gMFwq6QGDEBloAiXQw7z033ZSNTP
+p61QCePq6fLGEgdsfgjGotEbZ2XDZdB6fdzK+sKJLQtXr8FVrywyNmlX0bk8IRSs
+hnJXYco/VTLeDiQePO2bjm0UdQKBgQC4+0hR/se6AHgN9449ruHLSOCcC+Y6WQJ1
+BM+ydk7OnnfDmxzXQMbzri5kwDTBw+Y01avbRkpMZbHtWip9QWL96Kf8D6OUoX6d
+KJuheKsuJUKmXTPF8YyJiY+KXmx1yz7hu8X/SN955s4BAttIZGQvSIFpy+izzQWc
+FghcplvAgQKBgQCIkuIN37AOYFSPUU6PBMkkl11NWRG8bSM4Pq9GBuPpjvXX/f06
+f7lzo3J5E98FUlR1zzs3ZRgUX6RhorDvb1m81Mrtl3Bh51m1cjKwNhHB6aoHQo3j
+RBc3oJgGR0Q3Ny4TYRIm6yAk7ptGWwG1SLy/hKHyWOymvzsjq2gxgEPBNQKBgEyM
+6KvOBPdLVGNrS/jo01Yd7Z2GKxuAVEz61bzjys8ksylGmpPVob+cGGTnSa3aFP1O
+Y1VV7E9bUluIEcdN9NpgmovsKOTMRCpjcKxM1II/NyrDrTZANMmCHN3FH5tLpdUi
+sNhpXtoCksPGW9rEeNU8axnOIZmuwaCLWaCF07iBAoGAX494Slca2EnERTtcX9jq
+SM/srMTz+cm5zNA3npMj1eZ7zNglD5tZapc3f5OErrDdZz5wlmIo/eheQxb8TMQW
+F5goYHwpq6bghHlbAxK2I353s8Q8WriLTvAYouHfEqd14AS42OXD44CBwU0V5rRV
+MmlfOVBGqPVHLQK/tOebZIw=
+-----END PRIVATE KEY-----
diff --git a/public.pem b/public.pem
new file mode 100644
index 0000000..d8c25bf
--- /dev/null
+++ b/public.pem
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnPn6SRQ1JTwqFMFJq7Bg
+xMW/4V/HYSjHPbtKL/3jwXOTmOLUz4wr4Ib1IwiKpsguz0uyX1Ljbe3qQzqaCZu5
+hdoGo+uJsanz6yAoLYwETfIFIjqQr3FoIGQxYJDELkhSO8htbOKKoRiVKsjD5x4Q
+cEkZkLAaef8WVh08sEqE3fC4uLmOlSavycbMLmah9UdhljFcaVSQ7qox9HFvKF2e
+i4BmN2WdMGZ8JN20nc0OxoZpBpMArRqF0krjIkhA5/9CvIZEULPQChYL6AcmLS0j
+/yTxE82eFMBBvyeg8ICMRzcbCoN+hVnv3cb/7+BwNdoQjZqt0RG/hhbpc8RKt+YO
+9QIDAQAB
+-----END PUBLIC KEY-----
diff --git a/server.py b/server.py
new file mode 100644
index 0000000..a68efc9
--- /dev/null
+++ b/server.py
@@ -0,0 +1,87 @@
+from base64 import b64decode
+from cryptography.hazmat.primitives import hashes, serialization
+from cryptography.hazmat.primitives.asymmetric import padding
+from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
+from json import dump, load, loads
+from re import search
+from requests import get, post
+from os.path import isfile
+from urllib.parse import quote_plus
+
+
+def collection_append(file, item):
+ with open(file) as f:
+ collection = load(f)
+ collection['totalItems'] += 1
+ collection['orderedItems'].append(item)
+ with open(file, 'w') as f:
+ dump(collection, f)
+
+
+class fuwuqi(SimpleHTTPRequestHandler):
+ def do_POST(self):
+ body = self.rfile.read(int(self.headers['Content-Length']))
+ activity = loads(body)
+ print(activity)
+ print(self.headers)
+ print(self.path)
+
+ # Get actor public key
+ keyid = search('keyId="(.*?)"', self.headers['Signature']).group(1)
+ actorfile = f'users/{quote_plus(keyid)}'
+ if not isfile(actorfile):
+ with open(actorfile, 'w') as f:
+ f.write(get(keyid).text)
+ with open(actorfile) as f:
+ pubkeypem = load(f)['publicKey']['publicKeyPem'].encode('utf8')
+ pubkey = serialization.load_pem_public_key(pubkeypem, None)
+
+ # Assemble headers
+ headers = search('headers="(.*?)"', self.headers['Signature']).group(1)
+ message = ''
+ for header in headers.split():
+ headerval = self.headers[header]
+ message += f'{header}: {headerval}\n'
+
+ # Verify HTTP signature
+ signature = search('signature="(.*?)"', self.headers['Signature']).group(1)
+ pubkey.verify(
+ b64decode(signature),
+ message[:-1].encode('utf8'),
+ padding.PKCS1v15(),
+ hashes.SHA256()
+ )
+
+ # 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:
+ self.send_response(401)
+ return
+
+ username = search('^/users/(.*)\.(in|out)box$', self.path).group(1)
+ if self.path.endswith('inbox'):
+ # S2S
+ id = activity['id'].split('/')[-1]
+ with open(f'users/{username}/{id}', 'w') as f:
+ dump(activity, f)
+ elif self.path.endswith('outbox'):
+ # C2S
+ collection_append(f'users/{username}.outbox', activity)
+
+ if activity['type'] == 'Create':
+ id = activity['id'].split('/')[-1]
+ with open(f'users/{username}.statuses/{id}', 'w') as f:
+ dump(activity['object'], f)
+ print(self.headers)
+ print(body)
+ resp = post('https://social.exozy.me/inbox', headers=self.headers, data=body)
+ print(resp)
+ print(resp.text)
+
+ self.send_response(200)
+
+
+ThreadingHTTPServer(('localhost', 4200), fuwuqi).serve_forever()
diff --git a/users/https%3A%2F%2F0.exozy.me%2Fusers%2Ftest.jsonld%23main-key b/users/https%3A%2F%2F0.exozy.me%2Fusers%2Ftest.jsonld%23main-key
new file mode 100644
index 0000000..82cccc2
--- /dev/null
+++ b/users/https%3A%2F%2F0.exozy.me%2Fusers%2Ftest.jsonld%23main-key
@@ -0,0 +1,24 @@
+{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1"
+ ],
+ "id": "https://0.exozy.me/users/test.jsonld",
+ "type": "Person",
+ "preferredUsername": "test",
+ "name": "Billiam Wender",
+ "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",
+ "icon": {
+ "type": "Image",
+ "mediaType": "image/png",
+ "url": "https://0.exozy.me/users/test.png"
+ },
+ "publicKey": {
+ "id": "https://0.exozy.me/users/test.jsonld#main-key",
+ "owner": "https://0.exozy.me/users/test.jsonld",
+ "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnPn6SRQ1JTwqFMFJq7Bg\nxMW/4V/HYSjHPbtKL/3jwXOTmOLUz4wr4Ib1IwiKpsguz0uyX1Ljbe3qQzqaCZu5\nhdoGo+uJsanz6yAoLYwETfIFIjqQr3FoIGQxYJDELkhSO8htbOKKoRiVKsjD5x4Q\ncEkZkLAaef8WVh08sEqE3fC4uLmOlSavycbMLmah9UdhljFcaVSQ7qox9HFvKF2e\ni4BmN2WdMGZ8JN20nc0OxoZpBpMArRqF0krjIkhA5/9CvIZEULPQChYL6AcmLS0j\n/yTxE82eFMBBvyeg8ICMRzcbCoN+hVnv3cb/7+BwNdoQjZqt0RG/hhbpc8RKt+YO\n9QIDAQAB\n-----END PUBLIC KEY-----\n"
+ }
+}
diff --git a/users/https%3A%2F%2Fsocial.exozy.me%2Fusers%2Fa%23main-key b/users/https%3A%2F%2Fsocial.exozy.me%2Fusers%2Fa%23main-key
new file mode 100644
index 0000000..6fe1753
--- /dev/null
+++ b/users/https%3A%2F%2Fsocial.exozy.me%2Fusers%2Fa%23main-key
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (@a@exozy.me) - exocial
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/users/test.followers b/users/test.followers
new file mode 100644
index 0000000..a11f198
--- /dev/null
+++ b/users/test.followers
@@ -0,0 +1,6 @@
+{
+ "@context": "https://www.w3.org/ns/activitystreams",
+ "type": "OrderedCollection",
+ "totalItems": 0,
+ "orderedItems": [],
+}
diff --git a/users/test.following b/users/test.following
new file mode 100644
index 0000000..a11f198
--- /dev/null
+++ b/users/test.following
@@ -0,0 +1,6 @@
+{
+ "@context": "https://www.w3.org/ns/activitystreams",
+ "type": "OrderedCollection",
+ "totalItems": 0,
+ "orderedItems": [],
+}
diff --git a/users/test.inbox b/users/test.inbox
new file mode 100644
index 0000000..a11f198
--- /dev/null
+++ b/users/test.inbox
@@ -0,0 +1,6 @@
+{
+ "@context": "https://www.w3.org/ns/activitystreams",
+ "type": "OrderedCollection",
+ "totalItems": 0,
+ "orderedItems": [],
+}
diff --git a/users/test.jsonld b/users/test.jsonld
index 3ad47cb..82cccc2 100644
--- a/users/test.jsonld
+++ b/users/test.jsonld
@@ -9,6 +9,8 @@
"name": "Billiam Wender",
"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",
"icon": {
"type": "Image",
"mediaType": "image/png",
diff --git a/users/test.outbox b/users/test.outbox
new file mode 100644
index 0000000..bf8efb7
--- /dev/null
+++ b/users/test.outbox
@@ -0,0 +1 @@
+{"@context": "https://www.w3.org/ns/activitystreams", "type": "OrderedCollection", "totalItems": 22, "orderedItems": [{"@context": "https://www.w3.org/ns/activitystreams", "id": "https://0.exozy.me/users/test.outbox/create-hello-world", "type": "Create", "actor": "https://0.exozy.me/users/test.jsonld", "object": {"id": "https://0.exozy.me/users/test.outbox/hello-world", "type": "Note", "attributedTo": "https://0.exozy.me/users/test.jsonld", "inReplyTo": "https://social.exozy.me/@a/109707513227348721", "content": "Hello from fuwuqi!", "to": "https://www.w3.org/ns/activitystreams#Public"}}, {"@context": "https://www.w3.org/ns/activitystreams", "id": "https://0.exozy.me/users/test.outbox/hello-world", "type": "Create", "actor": "https://0.exozy.me/users/test.jsonld", "object": {"id": "https://0.exozy.me/users/test.statuses/hello-world", "type": "Note", "attributedTo": "https://0.exozy.me/users/test.jsonld", "inReplyTo": "https://social.exozy.me/@a/109707513227348721", "content": "Hello from fuwuqi!", "to": "https://www.w3.org/ns/activitystreams#Public"}}, {"@context": "https://www.w3.org/ns/activitystreams", "id": "https://0.exozy.me/users/test.outbox/hello-world", "type": "Create", "actor": "https://0.exozy.me/users/test.jsonld", "object": {"id": "https://0.exozy.me/users/test.statuses/hello-world", "type": "Note", "attributedTo": "https://0.exozy.me/users/test.jsonld", "inReplyTo": "https://social.exozy.me/@a/109707513227348721", "content": "Hello from fuwuqi!", "to": "https://www.w3.org/ns/activitystreams#Public"}}, {"@context": "https://www.w3.org/ns/activitystreams", "id": "https://0.exozy.me/users/test.outbox/hello-world", "type": "Create", "actor": "https://0.exozy.me/users/test.jsonld", "object": {"id": "https://0.exozy.me/users/test.statuses/hello-world", "type": "Note", "attributedTo": "https://0.exozy.me/users/test.jsonld", "inReplyTo": "https://social.exozy.me/@a/109707513227348721", "content": "Hello from fuwuqi!", "to": "https://www.w3.org/ns/activitystreams#Public"}}, {"@context": "https://www.w3.org/ns/activitystreams", "id": "https://0.exozy.me/users/test.outbox/hello-world", "type": "Create", "actor": "https://0.exozy.me/users/test.jsonld", "object": {"id": "https://0.exozy.me/users/test.statuses/hello-world", "type": "Note", "attributedTo": "https://0.exozy.me/users/test.jsonld", "inReplyTo": "https://social.exozy.me/@a/109707513227348721", "content": "Hello from fuwuqi!", "to": "https://www.w3.org/ns/activitystreams#Public"}}, {"@context": "https://www.w3.org/ns/activitystreams", "id": "https://0.exozy.me/users/test.outbox/hello-world", "type": "Create", "actor": "https://0.exozy.me/users/test.jsonld", "object": {"id": "https://0.exozy.me/users/test.statuses/hello-world", "type": "Note", "attributedTo": "https://0.exozy.me/users/test.jsonld", "inReplyTo": "https://social.exozy.me/@a/109707513227348721", "content": "Hello from fuwuqi!", "to": "https://www.w3.org/ns/activitystreams#Public"}}, {"@context": "https://www.w3.org/ns/activitystreams", "id": "https://0.exozy.me/users/test.outbox/hello-world", "type": "Create", "actor": "https://0.exozy.me/users/test.jsonld", "object": {"id": "https://0.exozy.me/users/test.statuses/hello-world", "type": "Note", "attributedTo": "https://0.exozy.me/users/test.jsonld", "inReplyTo": "https://social.exozy.me/@a/109707513227348721", "content": "Hello from fuwuqi!", "to": "https://www.w3.org/ns/activitystreams#Public"}}, {"@context": "https://www.w3.org/ns/activitystreams", "id": "https://0.exozy.me/users/test.outbox/hello-world", "type": "Create", "actor": "https://0.exozy.me/users/test.jsonld", "object": {"id": "https://0.exozy.me/users/test.statuses/hello-world", "type": "Note", "attributedTo": "https://0.exozy.me/users/test.jsonld", "inReplyTo": "https://social.exozy.me/@a/109707513227348721", "content": "Hello from fuwuqi!", "to": "https://www.w3.org/ns/activitystreams#Public"}}, {"@context": "https://www.w3.org/ns/activitystreams", "id": "https://0.exozy.me/users/test.outbox/hello-world", "type": "Create", "actor": "https://0.exozy.me/users/test.jsonld", "object": {"id": "https://0.exozy.me/users/test.statuses/hello-world", "type": "Note", "attributedTo": "https://0.exozy.me/users/test.jsonld", "inReplyTo": "https://social.exozy.me/@a/109707513227348721", "content": "Hello from fuwuqi!", "to": "https://www.w3.org/ns/activitystreams#Public"}}, {"@context": "https://www.w3.org/ns/activitystreams", "id": "https://0.exozy.me/users/test.outbox/hello-world", "type": "Create", "actor": "https://0.exozy.me/users/test.jsonld", "object": {"id": "https://0.exozy.me/users/test.statuses/hello-world", "type": "Note", "attributedTo": "https://0.exozy.me/users/test.jsonld", "inReplyTo": "https://social.exozy.me/@a/109707513227348721", "content": "Hello from fuwuqi!", "to": "https://www.w3.org/ns/activitystreams#Public"}}, {"@context": "https://www.w3.org/ns/activitystreams", "id": "https://0.exozy.me/users/test.outbox/hello-world", "type": "Create", "actor": "https://0.exozy.me/users/test.jsonld", "object": {"id": "https://0.exozy.me/users/test.statuses/hello-world", "type": "Note", "attributedTo": "https://0.exozy.me/users/test.jsonld", "inReplyTo": "https://social.exozy.me/@a/109707513227348721", "content": "Hello from fuwuqi!", "to": "https://www.w3.org/ns/activitystreams#Public"}}, {"@context": "https://www.w3.org/ns/activitystreams", "id": "https://0.exozy.me/users/test.outbox/hello-world", "type": "Create", "actor": "https://0.exozy.me/users/test.jsonld", "object": {"id": "https://0.exozy.me/users/test.statuses/hello-world", "type": "Note", "attributedTo": "https://0.exozy.me/users/test.jsonld", "inReplyTo": "https://social.exozy.me/@a/109707513227348721", "content": "Hello from fuwuqi!", "to": "https://www.w3.org/ns/activitystreams#Public"}}, {"@context": "https://www.w3.org/ns/activitystreams", "id": "https://0.exozy.me/users/test.outbox/hello-world", "type": "Create", "actor": "https://0.exozy.me/users/test.jsonld", "object": {"id": "https://0.exozy.me/users/test.statuses/hello-world", "type": "Note", "attributedTo": "https://0.exozy.me/users/test.jsonld", "inReplyTo": "https://social.exozy.me/@a/109707513227348721", "content": "Hello from fuwuqi!", "to": "https://www.w3.org/ns/activitystreams#Public"}}, {"@context": "https://www.w3.org/ns/activitystreams", "id": "https://0.exozy.me/users/test.outbox/hello-world", "type": "Create", "actor": "https://0.exozy.me/users/test.jsonld", "object": {"id": "https://0.exozy.me/users/test.statuses/hello-world", "type": "Note", "attributedTo": "https://0.exozy.me/users/test.jsonld", "inReplyTo": "https://social.exozy.me/@a/109707513227348721", "content": "Hello from fuwuqi!", "to": "https://www.w3.org/ns/activitystreams#Public"}}, {"@context": "https://www.w3.org/ns/activitystreams", "id": "https://0.exozy.me/users/test.outbox/hello-world", "type": "Create", "actor": "https://0.exozy.me/users/test.jsonld", "object": {"id": "https://0.exozy.me/users/test.statuses/hello-world", "type": "Note", "attributedTo": "https://0.exozy.me/users/test.jsonld", "inReplyTo": "https://social.exozy.me/@a/109707513227348721", "content": "Hello from fuwuqi!", "to": "https://www.w3.org/ns/activitystreams#Public"}}, {"@context": "https://www.w3.org/ns/activitystreams", "id": "https://0.exozy.me/users/test.outbox/hello-world", "type": "Create", "actor": "https://0.exozy.me/users/test.jsonld", "object": {"id": "https://0.exozy.me/users/test.statuses/hello-world", "type": "Note", "attributedTo": "https://0.exozy.me/users/test.jsonld", "inReplyTo": "https://social.exozy.me/@a/109707513227348721", "content": "Hello from fuwuqi!", "to": "https://www.w3.org/ns/activitystreams#Public"}}, {"@context": "https://www.w3.org/ns/activitystreams", "id": "https://0.exozy.me/users/test.outbox/hello-world", "type": "Create", "actor": "https://0.exozy.me/users/test.jsonld", "object": {"id": "https://0.exozy.me/users/test.statuses/hello-world", "type": "Note", "attributedTo": "https://0.exozy.me/users/test.jsonld", "inReplyTo": "https://social.exozy.me/@a/109707513227348721", "content": "Hello from fuwuqi!", "to": "https://www.w3.org/ns/activitystreams#Public"}}, {"@context": "https://www.w3.org/ns/activitystreams", "id": "https://0.exozy.me/users/test.outbox/hello-world", "type": "Create", "actor": "https://0.exozy.me/users/test.jsonld", "object": {"id": "https://0.exozy.me/users/test.statuses/hello-world", "type": "Note", "attributedTo": "https://0.exozy.me/users/test.jsonld", "inReplyTo": "https://social.exozy.me/@a/109707513227348721", "content": "Hello from fuwuqi!", "to": "https://www.w3.org/ns/activitystreams#Public"}}, {"@context": "https://www.w3.org/ns/activitystreams", "id": "https://0.exozy.me/users/test.outbox/hello-world", "type": "Create", "actor": "https://0.exozy.me/users/test.jsonld", "object": {"id": "https://0.exozy.me/users/test.statuses/hello-world", "type": "Note", "attributedTo": "https://0.exozy.me/users/test.jsonld", "inReplyTo": "https://social.exozy.me/@a/109707513227348721", "content": "Hello from fuwuqi!", "to": "https://www.w3.org/ns/activitystreams#Public"}}, {"@context": "https://www.w3.org/ns/activitystreams", "id": "https://0.exozy.me/users/test.outbox/hello-world2", "type": "Create", "actor": "https://0.exozy.me/users/test.jsonld", "object": {"id": "https://0.exozy.me/users/test.statuses/hello-world2", "type": "Note", "attributedTo": "https://0.exozy.me/users/test.jsonld", "inReplyTo": "https://social.exozy.me/@a/109707513227348721", "content": "Hello from fuwuqi! 2", "to": "https://www.w3.org/ns/activitystreams#Public"}}, {"@context": "https://www.w3.org/ns/activitystreams", "id": "https://0.exozy.me/users/test.outbox/hello-world2", "type": "Create", "actor": "https://0.exozy.me/users/test.jsonld", "object": {"id": "https://0.exozy.me/users/test.statuses/hello-world2", "type": "Note", "attributedTo": "https://0.exozy.me/users/test.jsonld", "inReplyTo": "https://social.exozy.me/@a/109707513227348721", "content": "Hello from fuwuqi! 2", "to": "https://www.w3.org/ns/activitystreams#Public"}}, {"@context": "https://www.w3.org/ns/activitystreams", "id": "https://0.exozy.me/users/test.outbox/hello-world2", "type": "Create", "actor": "https://0.exozy.me/users/test.jsonld", "object": {"id": "https://0.exozy.me/users/test.statuses/hello-world2", "type": "Note", "attributedTo": "https://0.exozy.me/users/test.jsonld", "inReplyTo": "https://social.exozy.me/@a/109707513227348721", "content": "Hello from fuwuqi! 2", "to": "https://www.w3.org/ns/activitystreams#Public"}}]}
\ No newline at end of file
diff --git a/users/test.statuses/hello-world b/users/test.statuses/hello-world
new file mode 100644
index 0000000..40905c9
--- /dev/null
+++ b/users/test.statuses/hello-world
@@ -0,0 +1 @@
+{"id": "https://0.exozy.me/users/test.statuses/hello-world", "type": "Note", "attributedTo": "https://0.exozy.me/users/test.jsonld", "inReplyTo": "https://social.exozy.me/@a/109707513227348721", "content": "Hello from fuwuqi!", "to": "https://www.w3.org/ns/activitystreams#Public"}
\ No newline at end of file
diff --git a/users/test.statuses/hello-world2 b/users/test.statuses/hello-world2
new file mode 100644
index 0000000..1c826d1
--- /dev/null
+++ b/users/test.statuses/hello-world2
@@ -0,0 +1 @@
+{"id": "https://0.exozy.me/users/test.statuses/hello-world2", "type": "Note", "attributedTo": "https://0.exozy.me/users/test.jsonld", "inReplyTo": "https://social.exozy.me/@a/109707513227348721", "content": "Hello from fuwuqi! 2", "to": "https://www.w3.org/ns/activitystreams#Public"}
\ No newline at end of file
--
cgit v1.2.3-70-g09d2