summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--COMMANDS.md3
-rw-r--r--README.md6
-rw-r--r--main.py497
4 files changed, 297 insertions, 213 deletions
diff --git a/.gitignore b/.gitignore
index a1b9cbb..bd5bbcd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
 .env
-__pycache__/
\ No newline at end of file
+__pycache__/
+users/
+portusers.py
\ No newline at end of file
diff --git a/COMMANDS.md b/COMMANDS.md
index 273e400..a1bd13b 100644
--- a/COMMANDS.md
+++ b/COMMANDS.md
@@ -1,5 +1,8 @@
 # Commands
 
+> **NOTE**
+> This has not been updated with recently added moderation commands!
+
 ## `register`
 Register an account.
 
diff --git a/README.md b/README.md
index 4309a8f..016a434 100644
--- a/README.md
+++ b/README.md
@@ -20,9 +20,9 @@ soktdeer rewrite
 - [x] get inbox
 - [x] client names
 ### moderation (from hydrogen)
-- [ ] bans
-- [ ] invite codes
-- [ ] kicking
+- [x] bans
+- [x] invite codes
+- [x] kicking
 - [ ] post to inbox
 - [ ] support codes
 - [ ] lockdown
diff --git a/main.py b/main.py
index b191e91..31035c5 100644
--- a/main.py
+++ b/main.py
@@ -1,6 +1,7 @@
 import asyncio

 import json

 import logging

+from websockets.exceptions import ConnectionClosed

 from websockets.asyncio.server import serve

 from websockets.asyncio.server import broadcast

 import re

@@ -120,250 +121,328 @@ class util:
                     "ulist": ulist

                 }))

 

-    def forcekick(username):

+    async def forcekick(username):

         if username in ulist:

             del ulist[username]

+            broadcast(clients, json.dumps({

+                "command": "ulist",

+                "ulist": ulist

+            }))

         for i in client_data.copy():

             if client_data[i]["username"] == username:

                 try:

-                    client_data[i]["websocket"].close()

+                    await client_data[i]["websocket"].close()

                 except:

                     pass

-                del client_data[i]

+                if i in client_data:

+                    del client_data[i]

 

 async def handler(websocket):

+    # make variables global to not cause problems later down the line

+    global invite_codes

+

     clients.append(websocket)

     await websocket.send(util.greeting())

-    async for message in websocket:

-        try:

-            r = json.loads(message)

-        except:

-            await websocket.send(util.error("malformedJson", None))

-            continue

-        if "listener" not in r:

-            r["listener"] = None

-        listener = r["listener"]

-        if "command" not in r:

-            await websocket.send(util.error("malformedJson", listener))

-            continue

-        if r["command"] == "register":

-            fc = util.field_check({"username": range(1,21), "password": range(8,256), "invite_code": range(16,17)}, r)

-            if fc != True:

-                await websocket.send(util.error(fc, listener))

-                continue

-            if str(websocket.id) in client_data:

-                await websocket.send(util.error("authed", listener))

-                continue

-            if locked:

-                await websocket.send(util.error("lockdown", listener))

+    try:

+        async for message in websocket:

+            try:

+                r = json.loads(message)

+            except:

+                await websocket.send(util.error("malformedJson", None))

                 continue

-            r["username"] = r["username"].lower()

-            r["invite_code"] = r["invite_code"].upper()

-            if not re.fullmatch("[a-z0-9-_]{1,20}", r["username"]):

-                await websocket.send(util.error("invalidUsername", listener))

-                continue

-            if r["invite_code"] not in invite_codes:

-                await websocket.send(util.error("invalidInvite", listener))

-                continue

-            if db.acc.get(r["username"]) != "notExists":

-                await websocket.send(util.error("usernameTaken", listener))

-                continue

-            data = {

-                "_id": str(uuid.uuid4()),

-                "username": r["username"],

-                "display_name": r["username"],

-                "created": round(time.time()),

-                "avatar": None,

-                "bot": False,

-                "verified": False,

-                "banned_until": 0,

-                "permissions": [],

-                "profile": {

-                    "bio": "",

-                    "lastfm": "",

-                    "banner": None,

-                    "links": {}

-                },

-                "secure": {

-                    "password": scrypt.hash(r["password"]),

-                    "token": secrets.token_urlsafe(64),

-                    "ban_reason": "",

-                    "invite_code": r["invite_code"],

-                    "support_code": secrets.token_hex(16)

-                }

-            }

-            result = db.acc.add(data)

-            if result != True:

-                await websocket.send(util.error(result, listener))

-                continue

-            invite_codes.remove(r["invite_code"])

-            await websocket.send(json.dumps({"error": False, "token": data["secure"]["token"], "listener": listener}))

-        elif r["command"] == "login_pswd":

-            fc = util.field_check({"username": range(1,21), "password": range(8,256)}, r)

-            if fc != True:

-                await websocket.send(util.error(fc, listener))

-                continue

-            if str(websocket.id) in client_data:

-                await websocket.send(util.error("authed", listener))

+            if "listener" not in r:

+                r["listener"] = None

+            listener = r["listener"]

+            if "command" not in r:

+                await websocket.send(util.error("malformedJson", listener))

                 continue

-            r["username"] = r["username"].lower()

-            if locked:

-                perms = db.acc.get_perms(r["username"])

-                if type(perms) != list:

-                    await websocket.send(util.error("lockdown", listener))

+            if r["command"] == "register":

+                fc = util.field_check({"username": range(1,21), "password": range(8,256), "invite_code": range(16,17)}, r)

+                if fc != True:

+                    await websocket.send(util.error(fc, listener))

                     continue

-                if "LOCK" not in perms:

+                if str(websocket.id) in client_data:

+                    await websocket.send(util.error("authed", listener))

+                    continue

+                if locked:

                     await websocket.send(util.error("lockdown", listener))

                     continue

-            valid = db.acc.verify_pswd(r["username"], r["password"])

-            if type(valid) == dict:

-                userdata = util.authorize(r["username"], str(websocket.id), websocket, r.get("client"))

-                await websocket.send(json.dumps({"error": False, "token": valid["token"], "user": userdata, "listener": listener}))

-                util.ulist()

-                continue

-            elif valid == "banned":

-                await websocket.send(util.error(valid, listener, db.acc.get_ban(r["username"])))

-                continue

-            else:

-                await websocket.send(util.error(valid, listener))

-                continue

-        elif r["command"] == "login_token":

-            fc = util.field_check({"token": range(32,128)}, r)

-            if fc != True:

-                await websocket.send(util.error(fc, listener))

-                continue

-            if str(websocket.id) in client_data:

-                await websocket.send(util.error("authed", listener))

-                continue

-            if locked:

-                await websocket.send(util.error("lockdown", listener))

-                continue

-            valid = db.acc.verify(r["token"])

-            if type(valid) == dict:

-                if valid["banned"]:

-                    await websocket.send(util.error("banned", listener, db.acc.get_ban(valid["username"])))

+                r["username"] = r["username"].lower()

+                r["invite_code"] = r["invite_code"].upper()

+                if not re.fullmatch("[a-z0-9-_]{1,20}", r["username"]):

+                    await websocket.send(util.error("invalidUsername", listener))

                     continue

-                else:

-                    userdata = util.authorize(valid["username"], str(websocket.id), websocket, r.get("client"))

-                    await websocket.send(json.dumps({"error": False, "user": userdata, "listener": listener}))

+                if r["invite_code"] not in invite_codes:

+                    await websocket.send(util.error("invalidInvite", listener))

+                    continue

+                if db.acc.get(r["username"]) != "notExists":

+                    await websocket.send(util.error("usernameTaken", listener))

+                    continue

+                data = {

+                    "_id": str(uuid.uuid4()),

+                    "username": r["username"],

+                    "display_name": r["username"],

+                    "created": round(time.time()),

+                    "avatar": None,

+                    "bot": False,

+                    "verified": False,

+                    "banned_until": 0,

+                    "permissions": [],

+                    "profile": {

+                        "bio": "",

+                        "lastfm": "",

+                        "banner": None,

+                        "links": {}

+                    },

+                    "secure": {

+                        "password": scrypt.hash(r["password"]),

+                        "token": secrets.token_urlsafe(64),

+                        "ban_reason": "",

+                        "invite_code": r["invite_code"],

+                        "support_code": secrets.token_hex(16)

+                    }

+                }

+                result = db.acc.add(data)

+                if result != True:

+                    await websocket.send(util.error(result, listener))

+                    continue

+                invite_codes.remove(r["invite_code"])

+                await websocket.send(json.dumps({"error": False, "token": data["secure"]["token"], "listener": listener}))

+            elif r["command"] == "login_pswd":

+                fc = util.field_check({"username": range(1,21), "password": range(8,256)}, r)

+                if fc != True:

+                    await websocket.send(util.error(fc, listener))

+                    continue

+                if str(websocket.id) in client_data:

+                    await websocket.send(util.error("authed", listener))

+                    continue

+                r["username"] = r["username"].lower()

+                if locked:

+                    perms = db.acc.get_perms(r["username"])

+                    if type(perms) != list:

+                        await websocket.send(util.error("lockdown", listener))

+                        continue

+                    if "LOCK" not in perms:

+                        await websocket.send(util.error("lockdown", listener))

+                        continue

+                valid = db.acc.verify_pswd(r["username"], r["password"])

+                if type(valid) == dict:

+                    userdata = util.authorize(r["username"], str(websocket.id), websocket, r.get("client"))

+                    await websocket.send(json.dumps({"error": False, "token": valid["token"], "user": userdata, "listener": listener}))

                     util.ulist()

                     continue

-            else:

-                await websocket.send(util.error(valid, listener))

-                continue

-        elif r["command"] == "get_user":

-            fc = util.field_check({"username": range(1,21)}, r)

-            if fc != True:

-                await websocket.send(util.error(fc, listener))

-                continue

-            if str(websocket.id) not in client_data:

-                await websocket.send(util.error("unauthorized", listener))

-                continue

-            data = db.acc.get(r["username"])

-            if type(data) != dict:

-                await websocket.send(util.error(data, listener))

-                continue

-            del data["secure"]

-            await websocket.send(json.dumps({"error": False, "user": data, "listener": listener}))

-            continue

-        elif r["command"] == "set_property":

-            fc = util.field_check({"property": range(1,64), "value": range(0,2048)}, r)

-            if fc != True:

-                await websocket.send(util.error(fc, listener))

-                continue

-            if str(websocket.id) not in client_data:

-                await websocket.send(util.error("unauthorized", listener))

-                continue

-            username = client_data[str(websocket.id)]["username"]

-            if r["property"] in ["bio", "lastfm"]:

-                result = db.acc.edit({f"profile.{r['property']}": r["value"]}, username)

-                if result:

-                    await websocket.send(json.dumps({"error": False, "listener": listener}))

+                elif valid == "banned":

+                    await websocket.send(util.error(valid, listener, db.acc.get_ban(r["username"])))

                     continue

                 else:

-                    await websocket.send(util.error("fail", listener))

+                    await websocket.send(util.error(valid, listener))

+                    continue

+            elif r["command"] == "login_token":

+                fc = util.field_check({"token": range(32,128)}, r)

+                if fc != True:

+                    await websocket.send(util.error(fc, listener))

                     continue

-            elif r["property"] == "display_name":

-                if not re.fullmatch("[a-zA-Z0-9-_,:🅱️. ]{1,20}", r["value"]):

-                    await websocket.send(util.error("invalidFormat", listener))

+                if str(websocket.id) in client_data:

+                    await websocket.send(util.error("authed", listener))

                     continue

-                result = db.acc.edit({r["property"]: r["value"]}, username)

-                if result:

-                    await websocket.send(json.dumps({"error": False, "listener": listener}))

+                if locked:

+                    await websocket.send(util.error("lockdown", listener))

                     continue

+                valid = db.acc.verify(r["token"])

+                if type(valid) == dict:

+                    if valid["banned"]:

+                        await websocket.send(util.error("banned", listener, db.acc.get_ban(valid["username"])))

+                        continue

+                    else:

+                        userdata = util.authorize(valid["username"], str(websocket.id), websocket, r.get("client"))

+                        await websocket.send(json.dumps({"error": False, "user": userdata, "listener": listener}))

+                        util.ulist()

+                        continue

                 else:

-                    await websocket.send(util.error("fail", listener))

+                    await websocket.send(util.error(valid, listener))

                     continue

-            elif r["property"] == "avatar":

-                if not urlparse(r["value"]).hostname in attachment_whitelist:

-                    await websocket.send(util.error("invalidFormat", listener))

+            elif r["command"] == "get_user":

+                fc = util.field_check({"username": range(1,21)}, r)

+                if fc != True:

+                    await websocket.send(util.error(fc, listener))

                     continue

-                result = db.acc.edit({r["property"]: r["value"]}, username)

-                if result:

-                    await websocket.send(json.dumps({"error": False, "listener": listener}))

+                if str(websocket.id) not in client_data:

+                    await websocket.send(util.error("unauthorized", listener))

                     continue

-                else:

-                    await websocket.send(util.error("fail", listener))

+                data = db.acc.get(r["username"])

+                if type(data) != dict:

+                    await websocket.send(util.error(data, listener))

                     continue

-            else:

-                await websocket.send(util.error("malformedJson", listener))

+                del data["secure"]

+                await websocket.send(json.dumps({"error": False, "user": data, "listener": listener}))

                 continue

-        elif r["command"] == "get_inbox":

-            if str(websocket.id) not in client_data:

-                await websocket.send(util.error("unauthorized", listener))

+            elif r["command"] == "set_property":

+                fc = util.field_check({"property": range(1,64), "value": range(0,2048)}, r)

+                if fc != True:

+                    await websocket.send(util.error(fc, listener))

+                    continue

+                if str(websocket.id) not in client_data:

+                    await websocket.send(util.error("unauthorized", listener))

+                    continue

+                username = client_data[str(websocket.id)]["username"]

+                if r["property"] in ["bio", "lastfm"]:

+                    result = db.acc.edit({f"profile.{r['property']}": r["value"]}, username)

+                    if result:

+                        await websocket.send(json.dumps({"error": False, "listener": listener}))

+                        continue

+                    else:

+                        await websocket.send(util.error("fail", listener))

+                        continue

+                elif r["property"] == "display_name":

+                    if not re.fullmatch("[a-zA-Z0-9-_,:🅱️. ]{1,20}", r["value"]):

+                        await websocket.send(util.error("invalidFormat", listener))

+                        continue

+                    result = db.acc.edit({r["property"]: r["value"]}, username)

+                    if result:

+                        await websocket.send(json.dumps({"error": False, "listener": listener}))

+                        continue

+                    else:

+                        await websocket.send(util.error("fail", listener))

+                        continue

+                elif r["property"] == "avatar":

+                    if not urlparse(r["value"]).hostname in attachment_whitelist:

+                        await websocket.send(util.error("invalidFormat", listener))

+                        continue

+                    result = db.acc.edit({r["property"]: r["value"]}, username)

+                    if result:

+                        await websocket.send(json.dumps({"error": False, "listener": listener}))

+                        continue

+                    else:

+                        await websocket.send(util.error("fail", listener))

+                        continue

+                else:

+                    await websocket.send(util.error("malformedJson", listener))

+                    continue

+            elif r["command"] == "gen_invite":

+                if str(websocket.id) not in client_data:

+                    await websocket.send(util.error("unauthorized", listener))

+                    continue

+                username = client_data[str(websocket.id)]["username"]

+                if "INVITE" not in db.acc.get_perms(username):

+                    await websocket.send(util.error("unauthorized", listener))

+                    continue

+                new_invite = secrets.token_hex(8).upper()

+                invite_codes.append(new_invite)

+                await websocket.send(json.dumps({"error": False, "invite_code": new_invite, "invite_codes": invite_codes, "listener": listener}))

                 continue

-            data = db.inbox.get_recent()

-            await websocket.send(json.dumps({"error": False, "inbox": data, "listener": listener}))

-        elif r["command"] == "post":

-            fc = util.field_check({"content": range(0,3001), "replies": range(0,4), "attachments": range(0,4)}, r)

-            if fc != True:

-                await websocket.send(util.error(fc, listener))

+            elif r["command"] == "reset_invites":

+                if str(websocket.id) not in client_data:

+                    await websocket.send(util.error("unauthorized", listener))

+                    continue

+                username = client_data[str(websocket.id)]["username"]

+                if "INVITE" not in db.acc.get_perms(username):

+                    await websocket.send(util.error("unauthorized", listener))

+                    continue

+                invite_codes = []

+                await websocket.send(json.dumps({"error": False, "listener": listener}))

                 continue

-            if str(websocket.id) not in client_data:

-                await websocket.send(util.error("unauthorized", listener))

+            elif r["command"] == "force_kick":

+                fc = util.field_check({"username": range(1,21)}, r)

+                if fc != True:

+                    await websocket.send(util.error(fc, listener))

+                    continue

+                if str(websocket.id) not in client_data:

+                    await websocket.send(util.error("unauthorized", listener))

+                    continue

+                username = client_data[str(websocket.id)]["username"]

+                if "KICK" not in db.acc.get_perms(username):

+                    await websocket.send(util.error("unauthorized", listener))

+                    continue

+                try:

+                    await util.forcekick(r["username"].lower())

+                except Exception as e:

+                    raise e

+                await websocket.send(json.dumps({"error": False, "listener": listener}))

                 continue

-            attachments = []

-            for i in r["attachments"]:

-                if urlparse(i).hostname in attachment_whitelist:

-                    attachments.append(i)

-            if len(r["content"]) == 0 and len(r["attachments"]) == 0:

-                await websocket.send(util.error("lengthInvalid", listener))

+            elif r["command"] == "ban":

+                fc = util.field_check({"username": range(1,21), "banned_until": range(0,0), "ban_reason": range(0,2001)}, r)

+                if fc != True:

+                    await websocket.send(util.error(fc, listener))

+                    continue

+                if str(websocket.id) not in client_data:

+                    await websocket.send(util.error("unauthorized", listener))

+                    continue

+                username = client_data[str(websocket.id)]["username"]

+                if "KICK" not in db.acc.get_perms(username):

+                    await websocket.send(util.error("unauthorized", listener))

+                    continue

+                r["username"] = r["username"].lower()

+                ac = db.acc.edit({"banned_until": r["banned_until"]}, r["username"])

+                if ac != True:

+                    server.send_message(client, util.error(ac, listener))

+                ac = db.acc.edit({"secure.ban_reason": r["ban_reason"]}, r["username"])

+                if ac != True:

+                    server.send_message(client, util.error(ac, listener))

+                if r["username"] in ulist:

+                    await util.forcekick(r["username"])

+                await websocket.send(json.dumps({"error": False, "listener": listener}))

                 continue

-            username = client_data[str(websocket.id)]["username"]

-            author = util.author_data(username)

-            replies = []

-            for i in r["replies"]:

-                post = db.posts.get_by_id(i)

-                if type(post) == dict:

-                    replies.append(post)

-            data = {

-                "_id": str(uuid.uuid4()),

-                "created": round(time.time()),

-                "content": r["content"],

-                "replies": replies,

-                "attachments": attachments,

-                "author": username

-            }

-            posted = db.posts.add(data)

-            if posted != True:

-                await websocket.send(util.error(fc, listener))

+            elif r["command"] == "get_inbox":

+                if str(websocket.id) not in client_data:

+                    await websocket.send(util.error("unauthorized", listener))

+                    continue

+                data = db.inbox.get_recent()

+                await websocket.send(json.dumps({"error": False, "inbox": data, "listener": listener}))

+            elif r["command"] == "post":

+                fc = util.field_check({"content": range(0,3001), "replies": range(0,4), "attachments": range(0,4)}, r)

+                if fc != True:

+                    await websocket.send(util.error(fc, listener))

+                    continue

+                if str(websocket.id) not in client_data:

+                    await websocket.send(util.error("unauthorized", listener))

+                    continue

+                attachments = []

+                for i in r["attachments"]:

+                    if urlparse(i).hostname in attachment_whitelist:

+                        attachments.append(i)

+                if len(r["content"]) == 0 and len(r["attachments"]) == 0:

+                    await websocket.send(util.error("lengthInvalid", listener))

+                    continue

+                username = client_data[str(websocket.id)]["username"]

+                author = util.author_data(username)

+                replies = []

+                for i in r["replies"]:

+                    post = db.posts.get_by_id(i)

+                    if type(post) == dict:

+                        replies.append(post)

+                data = {

+                    "_id": str(uuid.uuid4()),

+                    "created": round(time.time()),

+                    "content": r["content"],

+                    "replies": replies,

+                    "attachments": attachments,

+                    "author": username

+                }

+                posted = db.posts.add(data)

+                if posted != True:

+                    await websocket.send(util.error(fc, listener))

+                    continue

+                data["author"] = author

+                broadcast(clients, json.dumps({

+                    "command": "new_post",

+                    "data": data

+                }))

+            elif r["command"] == "ping":

+                pass

+            elif r["command"] in deprecated:

+                await websocket.send(util.error("deprecated", listener, {"replacement": deprecated[r["command"]]}))

                 continue

-            data["author"] = author

-            broadcast(clients, json.dumps({

-                "command": "new_post",

-                "data": data

-            }))

-        elif r["command"] == "ping":

-            pass

-        elif r["command"] in deprecated:

-            await websocket.send(util.error("deprecated", listener, {"replacement": deprecated[r["command"]]}))

-            continue

-        else:

-            await websocket.send(util.error("malformedJson", listener))

-    if str(websocket.id) in client_data:

-        util.loggedout(client_data[str(websocket.id)]["username"], str(websocket.id), websocket)

-    if websocket in clients:

-        clients.remove(websocket)

+            else:

+                await websocket.send(util.error("malformedJson", listener))

+    except ConnectionClosed:

+        pass

+    except Exception as e:

+        raise e

+    finally:

+        if str(websocket.id) in client_data:

+            util.loggedout(client_data[str(websocket.id)]["username"], str(websocket.id), websocket)

+        if websocket in clients:

+            clients.remove(websocket)

 

 async def main():

     async with serve(handler, addr, port) as server: