mirror of
https://git.exozy.me/a/fuwuqi.git
synced 2024-11-25 14:40:59 +00:00
Merge branch 'main' of git.exozy.me:a/fuwuqi
This commit is contained in:
commit
1e65c7b7ef
6 changed files with 56 additions and 28 deletions
|
@ -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:
|
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)
|
- 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`)
|
- Deleting posts
|
||||||
- Announcing posts, AKA boosting
|
|
||||||
- Unfollowing, unliking, etc (hint: implement `collection_remove`)
|
|
||||||
- JSON-LD (hint: don't do it, your brain will thank you)
|
- JSON-LD (hint: don't do it, your brain will thank you)
|
||||||
- Lots of pain
|
- Lots of pain
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,10 @@ from email.utils import formatdate
|
||||||
from requests import post
|
from requests import post
|
||||||
from sys import argv
|
from sys import argv
|
||||||
|
|
||||||
date = formatdate(usegmt=True)
|
|
||||||
|
|
||||||
with open(argv[1], 'rb') as f:
|
with open(argv[1], 'rb') as f:
|
||||||
activity = f.read()
|
activity = f.read()
|
||||||
|
|
||||||
|
date = formatdate(usegmt=True)
|
||||||
digester = hashes.Hash(hashes.SHA256())
|
digester = hashes.Hash(hashes.SHA256())
|
||||||
digester.update(activity)
|
digester.update(activity)
|
||||||
digest = b64encode(digester.finalize()).decode()
|
digest = b64encode(digester.finalize()).decode()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
"id": "https://0.exozy.me/users/test.outbox/follow",
|
"id": "https://0.exozy.me/users/test.outbox#follow",
|
||||||
"type": "Follow",
|
"type": "Follow",
|
||||||
"actor": "https://0.exozy.me/users/test.jsonld",
|
"actor": "https://0.exozy.me/users/test.jsonld",
|
||||||
"object": "https://social.exozy.me/users/guest",
|
"object": "https://social.exozy.me/users/guest",
|
||||||
|
|
72
server.py
72
server.py
|
@ -15,26 +15,43 @@ domain = 'https://0.exozy.me'
|
||||||
def collection_append(file, item):
|
def collection_append(file, item):
|
||||||
with open(file) as f:
|
with open(file) as f:
|
||||||
collection = load(f)
|
collection = load(f)
|
||||||
collection['totalItems'] += 1
|
|
||||||
collection['orderedItems'].append(item)
|
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:
|
with open(file, 'w') as f:
|
||||||
dump(collection, f)
|
dump(collection, f)
|
||||||
|
|
||||||
|
|
||||||
def iri_to_actor(iri):
|
def iri_to_actor(iri):
|
||||||
if domain in 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}'
|
actorfile = f'users/{name}'
|
||||||
else:
|
else:
|
||||||
actorfile = f'users/{quote_plus(iri)}'
|
actorfile = f'users/{quote_plus(iri.removesuffix("#main-key"))}'
|
||||||
if not isfile(actorfile):
|
if not isfile(actorfile):
|
||||||
with open(actorfile, 'w') as f:
|
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)
|
f.write(resp.text)
|
||||||
with open(actorfile) as f:
|
with open(actorfile) as f:
|
||||||
return load(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):
|
class fuwuqi(SimpleHTTPRequestHandler):
|
||||||
def do_POST(self):
|
def do_POST(self):
|
||||||
body = self.rfile.read(int(self.headers['Content-Length']))
|
body = self.rfile.read(int(self.headers['Content-Length']))
|
||||||
|
@ -72,10 +89,10 @@ class fuwuqi(SimpleHTTPRequestHandler):
|
||||||
|
|
||||||
# Make sure activity doer matches HTTP signature
|
# Make sure activity doer matches HTTP signature
|
||||||
actor = keyid.removesuffix('#main-key')
|
actor = keyid.removesuffix('#main-key')
|
||||||
if 'actor' in activity and activity['actor'] != actor:
|
if ('actor' in activity and activity['actor'] != actor) or
|
||||||
self.send_response(401)
|
('attributedTo' in activity and activity['attributedTo'] != actor) or
|
||||||
return
|
('actor' in activity['object'] and activity['object']['actor'] != actor) or
|
||||||
if 'attributedTo' in activity and activity['attributedTo'] != actor:
|
('attributedTo' in activity['object'] and activity['object']['attributedTo'] != actor)
|
||||||
self.send_response(401)
|
self.send_response(401)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -85,25 +102,36 @@ class fuwuqi(SimpleHTTPRequestHandler):
|
||||||
elif self.path.endswith('outbox'):
|
elif self.path.endswith('outbox'):
|
||||||
# C2S
|
# C2S
|
||||||
collection_append(f'users/{username}.outbox', activity)
|
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':
|
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:
|
with open(f'users/{username}.statuses/{id}', 'w') as f:
|
||||||
dump(activity['object'], f)
|
dump(activity['object'], f)
|
||||||
# Send to followers
|
elif activity['type'] == 'Accept':
|
||||||
with open(f'users/{username}.following') as f:
|
# Accept follow request
|
||||||
for followed in load(f)['orderedItems']:
|
collection_append(f'users/{username}.followers', activity['object']['actor'])
|
||||||
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'] == 'Follow':
|
elif activity['type'] == 'Follow':
|
||||||
object = iri_to_actor(activity['object'])
|
# Follow request
|
||||||
self.headers['Host'] = activity['object'].split('/')[2]
|
|
||||||
resp = post(object['inbox'], headers=self.headers, data=body)
|
|
||||||
print(resp)
|
|
||||||
print(resp.text)
|
|
||||||
collection_append(f'users/{username}.following', activity['object'])
|
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.send_response(200)
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
|
|
|
@ -7,10 +7,12 @@
|
||||||
"type": "Person",
|
"type": "Person",
|
||||||
"preferredUsername": "test",
|
"preferredUsername": "test",
|
||||||
"name": "Billiam Wender",
|
"name": "Billiam Wender",
|
||||||
|
"summary": "idk",
|
||||||
"inbox": "https://0.exozy.me/users/test.inbox",
|
"inbox": "https://0.exozy.me/users/test.inbox",
|
||||||
"outbox": "https://0.exozy.me/users/test.outbox",
|
"outbox": "https://0.exozy.me/users/test.outbox",
|
||||||
"followers": "https://0.exozy.me/users/test.followers",
|
"followers": "https://0.exozy.me/users/test.followers",
|
||||||
"following": "https://0.exozy.me/users/test.following",
|
"following": "https://0.exozy.me/users/test.following",
|
||||||
|
"liked": "https://0.exozy.me/users/test.liked",
|
||||||
"icon": {
|
"icon": {
|
||||||
"type": "Image",
|
"type": "Image",
|
||||||
"mediaType": "image/png",
|
"mediaType": "image/png",
|
||||||
|
|
1
users/test.liked
Normal file
1
users/test.liked
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"@context": "https://www.w3.org/ns/activitystreams", "type": "OrderedCollection", "totalItems": 0, "orderedItems": []}
|
Loading…
Reference in a new issue