commit b58967f77356283b89595ca5ae197e45ab97a6bc
parent a40113661478296e0ae8e0d8dfde4aab2c2bf00b
Author: Papayapaya <freifeld.david@gmail.com>
Date: Sat, 25 Jul 2026 15:25:48 -0700
Clean up formatting for readability
Diffstat:
| M | Cargo.toml | | | 2 | +- |
| M | README.md | | | 62 | ++++++++++++++++++++++++++++++++------------------------------ |
| M | test.py | | | 150 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------- |
3 files changed, 152 insertions(+), 62 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
@@ -2,5 +2,5 @@
members = [
"scuttlebutt",
- "chatterbox"
+ "chatterbox"
]
diff --git a/README.md b/README.md
@@ -15,13 +15,10 @@ brew install cassandra-cpp-driver
```
> **Warning**
-> This is a little wonky on M1 Macs: you'll need to follow the advice of
-> [this page](https://stackoverflow.com/questions/69486339/nativelibrarydarwin-java64-failed-to-link-the-c-library-against-jna-native-m)
-> when you face the inevitable JNA link error. Download for more recent JNA version is
-> [here](https://search.maven.org/artifact/net.java.dev.jna/jna/5.8.0/jar). I personally ran
-> `sudo mv jna-5.8.0.jar /opt/homebrew/Cellar/cassandra/4.0.6/libexec/jna-5.6.0.jar` (I
-> think...) once I downloaded the new version, and that fixed it.
-
+> This is a little wonky on M1 Macs: you'll need to follow the advice
+> of [this page](https://stackoverflow.com/q/69486339) when you face
+> the inevitable JNA link error. Download for more recent JNA version is
+> [here](https://search.maven.org/artifact/net.java.dev.jna/jna/5.8.0/jar).
You also need to install the Rust language:
```
@@ -35,8 +32,8 @@ Start by launching the database in the background with
cassandra -f
```
-Then, launch `chatterbox` - the service for sending/getting messages - and `scuttlebutt` -
-the service for everything else.
+Then, launch `chatterbox` - the service for sending/getting messages - and
+`scuttlebutt` - the service for everything else.
```
cargo run -p chatterbox &
@@ -51,36 +48,41 @@ Beyond basic text messaging, `blatherskite` has support for:
- Basic permissioning (owner/admin/none)
### Terminology
-Here's a quick guide to to the terms used by the service (that you might see in the
-`scuttlebutt` documentation):
+Here's a quick guide to to the terms used by the service (that you might see in
+the `scuttlebutt` documentation):
- Users can create or be invited to *groups* which contain *channels*.
- Groups have:
- *members*, the users who are part of the group
- - an *owner*, who made the group and is permitted to do specific actions (like deleting it)
- - *admin*, users who have elevated permissions for a group (like adding/removing channels)
-- *DMs* are a special kind of group that are made between users directly and limit certain
- functionality. DMs only have one channel and have no admin.
-- Channels also have *members* (which can be a subset of the group!). Channels by default
- are *public*, which means when a user is invited to a group they will be added to the
- channel. You can set them to *private* with another API call.
+ - an *owner*, who made the group and is permitted to do specific
+ actions (like deleting it)
+ - *admin*, users who have elevated permissions for a group (like
+ adding/removing channels)
+- *DMs* are a special kind of group that are made between users
+ directly and limit certain functionality. DMs only have one channel
+ and have no admin.
+- Channels also have *members* (which can be a subset of the
+ group!). Channels by default are *public*, which means when a user
+ is invited to a group they will be added to the channel. You can set
+ them to *private* with another API call.
## Scuttlebutt
-Scuttlebutt is an HTTP service that handles the creation, deletion, and updating of
-groups/channels/users as well as misc other actions.
+Scuttlebutt is an HTTP service that handles the creation, deletion, and updating
+of groups/channels/users as well as misc other actions.
-The various methods and objects are documented at `localhost:3000`, and the basic usage
-flow is something like:
-- `POST /api/user` to make a user, which will return a User object (see Schemas on the docs)
-- `GET /api/login` to login with said user. This will return a JWT that you'll use to
- authenticate future requests. This token will expire in a day!
-- Whatever requests you'd like at that point! Authenticate by including a `ScuttleKey`
- header with the token you got.
+The various methods and objects are documented at `localhost:3000`, and the
+basic usage flow is something like:
+- `POST /api/user` to make a user, which will return a User object (see Schemas
+ on the docs)
+- `GET /api/login` to login with said user. This will return a JWT that you'll
+ use to authenticate future requests. This token will expire in a day!
+- Whatever requests you'd like at that point! Authenticate by including a
+ `ScuttleKey` header with the token you got.
## Chatterbox
-Chatterbox is a websocket service used for sending and receiving messages. To use:
+Chatterbox is a websocket service used for sending and receiving messages:
- Connect to the websocket at `ws://localhost:3001/`
-- Send authentication in the form of `{"hash": "YOUR_PASSWORD_HASH", "id": "YOUR_ID"}`
+- Send authentication with `{"hash": "YOUR_PASSWORD_HASH", "id": "YOUR_ID"}`
- Then use the websocket as normal!
- - Send message requests in the form of `{"content": "whee", "channel": "CHANNEL_ID"}`
+ - Send message requests with `{"content": "whee", "channel": "CHANNEL_ID"}`
- Recieve messages!
diff --git a/test.py b/test.py
@@ -9,51 +9,112 @@ async def main():
# INIT CODE #
#############
- # same hash for everyone for succintness
+ # same hash for everyone for succinctness
HASH = "6c6e2b0cfda80007e693d52b5956083ea68770e1310d0ed02d195cb14113b284"
# login as quantum for init step
- r = requests.post(f'http://localhost:3000/api/user?name=quantum&email=test@example.com', data=HASH, headers={"Content-Type": "text/plain"})
+ r = requests.post(
+ f'http://localhost:3000/api/user?name=quantum&email=test@example.com',
+ data=HASH,
+ headers={"Content-Type": "text/plain"}
+ )
quantum = r.json()["id"]
- r = requests.post(f'http://localhost:3000/api/login?id={quantum}', data=HASH, headers={"Content-Type": "text/plain"})
+ r = requests.post(
+ f'http://localhost:3000/api/login?id={quantum}',
+ data=HASH,
+ headers={"Content-Type": "text/plain"}
+ )
tok = r.text
# init other users
- r = requests.post(f'http://localhost:3000/api/user?name=jemoka&email=test@example.com', data=HASH, headers={"Content-Type": "text/plain"})
+ r = requests.post(
+ f'http://localhost:3000/api/user?name=jemoka&email=test@example.com',
+ data=HASH,
+ headers={"Content-Type": "text/plain"}
+ )
jemoka = r.json()["id"]
- r = requests.post(f'http://localhost:3000/api/user?name=exr0n&email=test@example.com',data=HASH, headers={"Content-Type": "text/plain"})
+ r = requests.post(
+ f'http://localhost:3000/api/user?name=exr0n&email=test@example.com',
+ data=HASH,
+ headers={"Content-Type": "text/plain"}
+ )
exr0n = r.json()["id"]
- r = requests.post(f'http://localhost:3000/api/user?name=enquirer&email=test@example.com', data=HASH, headers={"Content-Type": "text/plain"})
+ r = requests.post(
+ f'http://localhost:3000/api/user?name=enquirer&email=test@example.com',
+ data=HASH,
+ headers={"Content-Type": "text/plain"}
+ )
enquirer = r.json()["id"]
- r = requests.post(f'http://localhost:3000/api/user?name=zbuster&email=test@example.com', data=HASH, headers={"Content-Type": "text/plain"})
+ r = requests.post(
+ f'http://localhost:3000/api/user?name=zbuster&email=test@example.com',
+ data=HASH,
+ headers={"Content-Type": "text/plain"}
+ )
zbuster = r.json()["id"]
# create group with multiple members
- r = requests.post(f'http://localhost:3000/api/group?name=testing', headers={"Authorization": tok})
+ r = requests.post(
+ f'http://localhost:3000/api/group?name=testing',
+ headers={"Authorization": tok}
+ )
testing = r.json()["id"]
- r = requests.put(f'http://localhost:3000/api/group/members?gid={testing}&uid={zbuster}', headers={"Authorization": tok})
- r = requests.put(f'http://localhost:3000/api/group/members?gid={testing}&uid={exr0n}', headers={"Authorization": tok})
- r = requests.put(f'http://localhost:3000/api/group/members?gid={testing}&uid={jemoka}', headers={"Authorization": tok})
-
+ r = requests.put(
+ f'http://localhost:3000/api/group/members?gid={testing}&uid={zbuster}',
+ headers={"Authorization": tok}
+ )
+ r = requests.put(
+ f'http://localhost:3000/api/group/members?gid={testing}&uid={exr0n}',
+ headers={"Authorization": tok}
+ )
+ r = requests.put(
+ f'http://localhost:3000/api/group/members?gid={testing}&uid={jemoka}',
+ headers={"Authorization": tok}
+ )
# create another two groups
- r = requests.post(f'http://localhost:3000/api/group?name=whoo', headers={"Authorization": tok})
+ r = requests.post(
+ f'http://localhost:3000/api/group?name=whoo',
+ headers={"Authorization": tok}
+ )
whoo = r.json()["id"]
- r = requests.put(f'http://localhost:3000/api/group/members?gid={whoo}&uid={zbuster}', headers={"Authorization": tok})
- r = requests.put(f'http://localhost:3000/api/group/members?gid={whoo}&uid={enquirer}', headers={"Authorization": tok})
+ r = requests.put(
+ f'http://localhost:3000/api/group/members?gid={whoo}&uid={zbuster}',
+ headers={"Authorization": tok}
+ )
+ r = requests.put(
+ f'http://localhost:3000/api/group/members?gid={whoo}&uid={enquirer}',
+ headers={"Authorization": tok}
+ )
- r = requests.post(f'http://localhost:3000/api/group?name=whee', headers={"Authorization": tok})
+ r = requests.post(
+ f'http://localhost:3000/api/group?name=whee',
+ headers={"Authorization": tok}
+ )
whee = r.json()["id"]
- r = requests.put(f'http://localhost:3000/api/group/members?gid={whee}&uid={enquirer}', headers={"Authorization": tok})
- r = requests.put(f'http://localhost:3000/api/group/members?gid={whee}&uid={jemoka}', headers={"Authorization": tok})
+ r = requests.put(
+ f'http://localhost:3000/api/group/members?gid={whee}&uid={enquirer}',
+ headers={"Authorization": tok}
+ )
+ r = requests.put(
+ f'http://localhost:3000/api/group/members?gid={whee}&uid={jemoka}',
+ headers={"Authorization": tok}
+ )
##############################
# INTEGRATION TEST 1: GROUPS #
##############################
# log two users in
- r = requests.post(f'http://localhost:3000/api/login?id={zbuster}', data=HASH, headers={"Content-Type": "text/plain"})
+ r = requests.post(
+ f'http://localhost:3000/api/login?id={zbuster}',
+ data=HASH,
+ headers={"Content-Type": "text/plain"}
+ )
z_tok = r.text
- r = requests.post(f'http://localhost:3000/api/login?id={jemoka}', data=HASH, headers={"Content-Type": "text/plain"})
+ r = requests.post(
+ f'http://localhost:3000/api/login?id={jemoka}',
+ data=HASH,
+ headers={"Content-Type": "text/plain"}
+ )
j_tok = r.text
# init websockets for both
@@ -63,21 +124,30 @@ async def main():
await j_sock.send(f'{{"hash": "{HASH}", "id": {jemoka}}}')
# check zbuster's groups
- r = requests.get(f'http://localhost:3000/api/user/groups', headers={"Authorization": z_tok})
+ r = requests.get(
+ f'http://localhost:3000/api/user/groups',
+ headers={"Authorization": z_tok}
+ )
assert(len(r.json()) == 2)
group_ids = list(map(lambda x: x["id"], r.json()))
assert(testing in group_ids)
assert(whoo in group_ids)
# check jemoka's groups
- r = requests.get(f'http://localhost:3000/api/user/groups', headers={"Authorization": j_tok})
+ r = requests.get(
+ f'http://localhost:3000/api/user/groups',
+ headers={"Authorization": j_tok}
+ )
assert(len(r.json()) == 2)
group_ids = list(map(lambda x: x["id"], r.json()))
assert(testing in group_ids)
assert(whee in group_ids)
# get + check main channel of testing
- r = requests.get(f'http://localhost:3000/api/group?id={testing}', headers={"Authorization": z_tok})
+ r = requests.get(
+ f'http://localhost:3000/api/group?id={testing}',
+ headers={"Authorization": z_tok}
+ )
testing_main = r.json()["channels"][0]
assert(r.json()["name"] == "testing")
assert(r.json()["is_dm"] == False)
@@ -100,11 +170,18 @@ async def main():
assert(j_msg["channel"] == testing_main)
# log another user in
- r = requests.post(f'http://localhost:3000/api/login?id={enquirer}', data=HASH, headers={"Content-Type": "text/plain"})
+ r = requests.post(
+ f'http://localhost:3000/api/login?id={enquirer}',
+ data=HASH,
+ headers={"Content-Type": "text/plain"}
+ )
h_tok = r.text
# attempt invalid action
- r = requests.delete(f'http://localhost:3000/api/group?id={testing}', headers={"Authorization": h_tok})
+ r = requests.delete(
+ f'http://localhost:3000/api/group?id={testing}',
+ headers={"Authorization": h_tok}
+ )
assert(r.status_code == 401)
print("Test 1 done!")
@@ -113,7 +190,11 @@ async def main():
##########################
# log yet another user in
- r = requests.post(f'http://localhost:3000/api/login?id={exr0n}', data=HASH, headers={"Content-Type": "text/plain"})
+ r = requests.post(
+ f'http://localhost:3000/api/login?id={exr0n}',
+ data=HASH,
+ headers={"Content-Type": "text/plain"}
+ )
e_tok = r.text
# init websockets for both exr0n and enquirer
@@ -123,7 +204,10 @@ async def main():
await h_sock.send(f'{{"hash": "{HASH}", "id": {enquirer}}}')
# create dm
- r = requests.post(f'http://localhost:3000/api/dm?uid={exr0n}', headers={"Authorization": h_tok})
+ r = requests.post(
+ f'http://localhost:3000/api/dm?uid={exr0n}',
+ headers={"Authorization": h_tok}
+ )
dm = r.json()["id"]
assert(r.json()["name"] == "")
assert(r.json()["is_dm"] == True)
@@ -135,12 +219,18 @@ async def main():
dm_main = r.json()["channels"][0]
# check that exr0n can see it
- r = requests.get(f'http://localhost:3000/api/user/dms', headers={"Authorization": e_tok})
+ r = requests.get(
+ f'http://localhost:3000/api/user/dms',
+ headers={"Authorization": e_tok}
+ )
assert(len(r.json()) == 1)
assert(dm == r.json()[0]["id"])
# and enquirer for that matter
- r = requests.get(f'http://localhost:3000/api/user/dms', headers={"Authorization": h_tok})
+ r = requests.get(
+ f'http://localhost:3000/api/user/dms',
+ headers={"Authorization": h_tok}
+ )
assert(len(r.json()) == 1)
assert(dm == r.json()[0]["id"])
# test basic messaging
@@ -152,8 +242,6 @@ async def main():
print("Test 2 done!")
-
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait_for(main(), 5))
-# asyncio.run(main(), timeout=5)