diff --git a/.gitignore b/.gitignore index 9757c82..0ce25c6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -/out -/.vs \ No newline at end of file + +/__pycache__ diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 38974db..0000000 --- a/.gitmodules +++ /dev/null @@ -1,9 +0,0 @@ -[submodule "lib/websocketpp"] - path = lib/websocketpp - url = https://github.com/zaphoyd/websocketpp -[submodule "lib/zlib"] - path = lib/zlib - url = https://github.com/madler/zlib -[submodule "lib/uWebSockets"] - path = lib/uWebSockets - url = https://github.com/uNetworking/uWebSockets diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index a4925dc..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -cmake_minimum_required(VERSION 3.12) - -project("Kahoot") - -set(CMAKE_CXX_STANDARD 20) - -add_executable(Kahoot "src/main.cpp") - -include_directories("lib/uWebSockets/src") -include_directories("lib/uWebSockets/uSockets/src") -include_directories("lib/zlib") \ No newline at end of file diff --git a/CMakePresets.json b/CMakePresets.json deleted file mode 100644 index e64126c..0000000 --- a/CMakePresets.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "version": 3, - "configurePresets": [ - { - "name": "x64-release", - "displayName": "x64 Release", - "generator": "Ninja", - "binaryDir": "${sourceDir}/out/build/${presetName}", - "installDir": "${sourceDir}/out/install/${presetName}", - "architecture": { - "value": "x64", - "strategy": "external" - }, - "cacheVariables": { - "CMAKE_C_COMPILER": "cl.exe", - "CMAKE_CXX_COMPILER": "cl.exe", - "CMAKE_BUILD_TYPE": "Release" - }, - "condition": { - "type": "equals", - "lhs": "${hostSystemName}", - "rhs": "Windows" - } - }, - { - "name": "linux-release", - "displayName": "Linux Release", - "generator": "Ninja", - "binaryDir": "${sourceDir}/out/build/${presetName}", - "installDir": "${sourceDir}/out/install/${presetName}", - "cacheVariables": { - "CMAKE_BUILD_TYPE": "Release" - }, - "condition": { - "type": "equals", - "lhs": "${hostSystemName}", - "rhs": "Linux" - }, - "vendor": { - "microsoft.com/VisualStudioRemoteSettings/CMake/1.0": { - "sourceDir": "$env{HOME}/.vs/$ms{projectDirName}" - } - } - } - ] -} diff --git a/LICENSE b/LICENSE deleted file mode 100644 index c47a018..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 KaseToatz1337 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md deleted file mode 100644 index 8742c86..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# Kahoot -A kahoot flooder because I was bored very fast very gud diff --git a/app.py b/app.py new file mode 100644 index 0000000..b961694 --- /dev/null +++ b/app.py @@ -0,0 +1,127 @@ +import re +import aiohttp +import time +import py_mini_racer +import base64 +import uvicorn +import websockets +import json +import asyncio +import random +import itertools + +from fastapi import FastAPI, Form +from fastapi.responses import JSONResponse +from fastapi.middleware.cors import CORSMiddleware + +class App(FastAPI): + + def __init__(self) -> None: + super().__init__(title="Kahoot API", openapi_url=None) + self.add_middleware(CORSMiddleware, allow_origins=["*"]) + self.semaphore = asyncio.Semaphore(10) + + async def connect(self, pin: int, name: str, number: int, namerator: bool = False) -> None: + try: + async with self.semaphore: + async with aiohttp.ClientSession() as session: + if namerator: + async with session.get("https://apis.kahoot.it/") as response: + name = (await response.json(content_type=None))["name"] + async with session.get(f"https://play.kahoot.it/reserve/session/{pin}/?{int(time.time() * 1000)}") as response: + sessionToken = response.headers["x-kahoot-session-token"] + challenge = (await response.json())["challenge"] + text = re.split("[{};]", challenge.replace("\t", "").encode("ascii", "ignore").decode()) + solChars = [ord(s) for s in py_mini_racer.MiniRacer().eval("".join([text[1] + "{", text[2] + ";", "return message.replace(/./g, function(char, position) {", text[7] + ";})};", text[0]]))] + sessChars = [ord(s) for s in base64.b64decode(sessionToken).decode()] + sessionID = "".join([chr(sessChars[i] ^ solChars[i % len(solChars)]) for i in range(len(sessChars))]) + ws = await websockets.connect(f"wss://play.kahoot.it/cometd/{pin}/{sessionID}", open_timeout=None, ping_timeout=None, close_timeout=None) + await ws.send(json.dumps([{"channel": "/meta/handshake"}])) + clientID = json.loads(await ws.recv())[0]["clientId"] + await ws.send(json.dumps([ + { + "channel": "/service/controller", + "data": { + "type": "login", + "gameid": pin, + "name": name, + }, + "clientId": clientID, + } + ])) + await ws.send(json.dumps([ + { + "channel": "/service/controller", + "data": { + "type": "message", + "gameid": pin, + "id": 16, + }, + "clientId": clientID, + } + ])) + while True: + message = json.loads(await ws.recv())[0] + if message.get("data", {}).get("id", None) == 2: + question = json.loads(message["data"]["content"]) + if question["type"] == "quiz": + await asyncio.sleep(number / 100) + await ws.send(json.dumps([ + { + "channel": "/service/controller", + "data": { + "type": "message", + "gameid": pin, + "id": 45, + "content": json.dumps({"type": "quiz", "choice": random.randint(0, question["numberOfChoices"] - 1), "questionIndex": question["gameBlockIndex"]}) + }, + "clientId": clientID, + } + ])) + elif question["type"] == "multiple_select_quiz": + await asyncio.sleep(number / 100) + await ws.send(json.dumps([ + { + "channel": "/service/controller", + "data": { + "type": "message", + "gameid": pin, + "id": 45, + "content": json.dumps({"type": "multiple_select_quiz", "choice": [random.randint(0, question["numberOfChoices"] - 1)], "questionIndex": question["gameBlockIndex"]}) + }, + "clientId": clientID, + } + ])) + elif message.get("data", {}).get("id", None) in [10, 13] or message.get("data", {}).get("reason", None) == "disconnect": + await ws.close() + except websockets.ConnectionClosed: + return + except: + if "ws" in locals().keys(): + await ws.close() + +app = App() + +@app.post("/flood", response_class=JSONResponse) +async def flood(pin: int = Form(0), naming: str = Form(""), name: str = Form(""), amount: int = Form(0)) -> JSONResponse: + async with aiohttp.ClientSession() as session: + async with session.get(f"https://play.kahoot.it/reserve/session/{pin}/?{int(time.time() * 1000)}") as response: + if "x-kahoot-session-token" not in response.headers: + return JSONResponse({"message": "Invalid PIN specified.", "type": "error"}, 400) + if len(name) < 2 or len(name) > 15: + return JSONResponse({"message": "Invalid name specified.", "type": "error"}, 400) + if amount < 1 or amount > 2000: + return JSONResponse({"message": "Invalid amount specified.", "type": "error"}, 400) + if naming == "enumerated": + await asyncio.gather(*[app.connect(pin, f"{name[:15-len(str(i))]}{i}", i) for i in range(amount)]) + elif naming == "capitalized": + names = list(map(''.join, itertools.product(*zip(name.upper(), name.lower())))) + await asyncio.gather(*[app.connect(pin, names[i], i) for i in range(amount)]) + elif naming == "random": + await asyncio.gather(*[app.connect(pin, str(i), i, True) for i in range(amount)]) + else: + return JSONResponse({"message": "Invalid naming method specified.", "type": "error"}, 400) + return {"message": "Game finished.", "type": "success"} + +if __name__ == "__main__": + uvicorn.run("app:app", host="0.0.0.0", port=80) \ No newline at end of file diff --git a/html/assets/logo.svg b/html/assets/logo.svg new file mode 100644 index 0000000..41b208a --- /dev/null +++ b/html/assets/logo.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/html/assets/script.js b/html/assets/script.js new file mode 100644 index 0000000..f33702a --- /dev/null +++ b/html/assets/script.js @@ -0,0 +1,61 @@ +window.onload = () => { + var pin = document.getElementById("pin"); + var naming = document.getElementById("naming"); + var name = document.getElementById("name"); + var amount = document.getElementById("amount"); + var flood = document.getElementById("flood"); + var text = document.getElementById("text"); + + function checkMaxCaps() + { + if (naming.value == "capitalized") + { + var maxCaps = 1 << name.value.length; + if (amount.value > maxCaps) + { + text.innerText = `Amount will be capped at ${maxCaps}.`; + text.style.color = "red"; + } + else + { + text.innerText = ""; + text.style.color = "black"; + } + } + } + + naming.onchange = () => { + if (naming.value == "random") + { + name.disabled = true; + } + else + { + name.disabled = false; + checkMaxCaps(); + } + } + + amount.oninput = () => {checkMaxCaps()}; + name.oninput = () => {checkMaxCaps()}; + + flood.onclick = async () => { + var formData = new FormData(); + formData.append("pin", parseInt(pin.value)); + formData.append("naming", naming.value) + formData.append("name", name.value); + formData.append("amount", parseInt(amount.value)); + text.value = "Flooding in progress..."; + text.style.color = "green"; + var request = await fetch("http://127.0.0.1/flood", { + method: "POST", + body: formData + }) + response = await request.json(); + text.value = response.message; + if (response.type == "error") + { + text.style.color = "red"; + } + } +} \ No newline at end of file diff --git a/html/assets/style.css b/html/assets/style.css new file mode 100644 index 0000000..ec9b1ed --- /dev/null +++ b/html/assets/style.css @@ -0,0 +1,117 @@ +body +{ + margin: 0px; + display: flex; + position: absolute; + width: 100%; + height: 100%; + align-items: center; + justify-content: center; + background-color: rgb(56, 18, 114); +} + +#container +{ + display: flex; + flex-direction: column; +} + +input +{ + outline: none; +} + +#rectangle +{ + position: fixed; + background-color: black; + transform: rotate(45deg); + opacity: 0.1; + top: -15vmin; + left: -15vmin; + min-width: 75vmin; + min-height: 75vmin; + z-index: -1; +} + +#circle +{ + position: fixed; + border-radius: 50%; + background-color: black; + opacity: 0.1; + bottom: -15vmin; + right: -15vmin; + min-width: 75vmin; + min-height: 75vmin; + z-index: -1; +} + +#logo +{ + background-image: url("/html/assets/logo.svg"); + height: 80px; + background-repeat: no-repeat; + background-position: center; + margin-bottom: 30px; +} + +#form +{ + display: flex; + flex-direction: column; + gap: 5px; + padding: 20px; + background-color: white; + border-radius: 5px; +} + +input, select +{ + outline: none; + height: 40px; + text-align: center; + font-size: 1rem; + font-weight: 700; + color: rgb(51, 51, 51); + border: 0.125rem solid rgb(204, 204, 204); + border-radius: 5px; + background-color: white; + transition: border-color 0.25s; +} + +select +{ + color: #9191A0; + height: 46px; +} + +input:focus:hover, select:focus:hover +{ + border-color: rgb(51, 51, 51); +} + +input:focus, select:focus +{ + border-color: #0060DF; +} + +#flood +{ + background-color: rgb(51, 51, 51); + color: white; + border: none; + box-shadow: rgba(0, 0, 0, 0.25) 0px -4px inset; + padding: 0px 16px 4px; + min-height: 46px; + cursor: pointer; +} + +#flood:hover +{ + background-color: rgb(47, 47, 47); + box-shadow: rgba(0, 0, 0, 0.25) 0px -2px inset; + margin-top: 2px; + padding-bottom: 2px; + min-height: 44px; +} \ No newline at end of file diff --git a/html/index.html b/html/index.html new file mode 100644 index 0000000..083e101 --- /dev/null +++ b/html/index.html @@ -0,0 +1,32 @@ + + + + Kahoot Flooder + + + + + + + +
+
+
+ +
+ +
+ + +
+ + +
+

+
+ + \ No newline at end of file diff --git a/lib/uWebSockets b/lib/uWebSockets deleted file mode 160000 index 84941b9..0000000 --- a/lib/uWebSockets +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 84941b99bf32dffafd940ad5a4a92d9c007a086d diff --git a/lib/zlib b/lib/zlib deleted file mode 160000 index 0f51fb4..0000000 --- a/lib/zlib +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0f51fb4933fc9ce18199cb2554dacea8033e7fd3 diff --git a/src/main.cpp b/src/main.cpp deleted file mode 100644 index 5ef01ba..0000000 --- a/src/main.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "ClientApp.h" - -int main() -{ - return 0; -} \ No newline at end of file