rewrite
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,2 +1,2 @@
|
||||
/out
|
||||
/.vs
|
||||
|
||||
/__pycache__
|
||||
|
9
.gitmodules
vendored
9
.gitmodules
vendored
@ -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
|
@ -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")
|
@ -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}"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
21
LICENSE
21
LICENSE
@ -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.
|
@ -1,2 +0,0 @@
|
||||
# Kahoot
|
||||
A kahoot flooder because I was bored very fast very gud
|
127
app.py
Normal file
127
app.py
Normal file
@ -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)
|
14
html/assets/logo.svg
Normal file
14
html/assets/logo.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 440 150" enable-background="new 0 0 440 150" xml:space="preserve">
|
||||
<g>
|
||||
<path fill="#FFFFFF" d="M226.8,52.5c-18.7,0.3-34.8,16.1-36,35.1c-1.1,19.1,14.1,36.1,33.9,38.1c19.9,2,36-13.8,36-35.1 C260.7,69.2,245.5,52.2,226.8,52.5z M240.2,92c-0.8,9.9-7,15.3-11.1,15.7c-10.3,1.2-19.1-8.1-18.5-20s5.4-19.3,15.1-19.5 C235.4,68,241.3,79.1,240.2,92z"/>
|
||||
<path fill="#FFFFFF" d="M300.3,54.4c-18.7-0.3-33.9,16.7-33.9,38.1s16.1,37.1,36,35.1c19.9-2,35.1-19,33.9-38.1 C335.2,70.5,319.1,54.8,300.3,54.4z M298,109.7c-4.1-0.5-10.2-5.9-11.1-15.7c-1.1-12.9,4.8-24,14.5-23.8s14.5,7.6,15.1,19.5 C317.1,101.5,308.3,110.9,298,109.7z"/>
|
||||
<polygon fill="#FFFFFF" points="82.5,36.6 62.4,28.9 23.2,66.3 23.2,19.6 0,25.5 0,135.2 23.2,136 23,97.6 37.3,83.8 52.4,136 72.9,136 54,67.9 "/>
|
||||
<path fill="#FFFFFF" d="M146.2,49.2L145.8,0L126,5.2l3.2,121.6l19.5,0.7l-1.1-62.8c4.3-1.9,18.4-5.8,18.9,9.7l1.9,22.1l1.3,31.1 h20.4l-7.7-53.7C179.3,42.5,167.8,42.4,146.2,49.2z"/>
|
||||
<polygon fill="#FFFFFF" points="415.2,128.2 409.5,141.6 421.7,150 433.7,142.9 428.6,128.2 "/>
|
||||
<path fill="#FFFFFF" d="M363.4,19.6l-15.5-6.4v32.1l-17.3-0.6l3.3,22.8h14l1,54.3c0,0-2.2,19.5,27.6,15.1c0,0,9.3-2.8,9-9.3v-21.4 c0,0-6.3,3.7-13.1,3.7c-6.8,0-6.9-3.3-6.9-3.3l-1.8-42l21.7-1.2V49.5L363.1,48L363.4,19.6z"/>
|
||||
<polygon fill="#FFFFFF" points="440,19.8 399,12 424.2,120.5 "/>
|
||||
<path fill="#FFFFFF" d="M77.6,49.9L83,64.1c13.5-8.1,21.1,0,21.1,0l-0.1,9.1c-40.2,7.1-30,44.3-30,44.3 c4.1,14.6,17.3,14.1,17.3,14.1h30.1l0.6-67.7C117.3,32.4,77.6,49.9,77.6,49.9z M104,118.6c0,0-14.6,3.5-16.2-10.7 c0,0,0.3-22,16.7-19.6L104,118.6z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
61
html/assets/script.js
Normal file
61
html/assets/script.js
Normal file
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
117
html/assets/style.css
Normal file
117
html/assets/style.css
Normal file
@ -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;
|
||||
}
|
32
html/index.html
Normal file
32
html/index.html
Normal file
@ -0,0 +1,32 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="EN">
|
||||
<head>
|
||||
<title>Kahoot Flooder</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href='https://fonts.googleapis.com/css?family=Montserrat' rel='stylesheet'>
|
||||
<link rel="stylesheet" href="assets/style.css">
|
||||
<script type="text/javascript" src="assets/script.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="rectangle"></div>
|
||||
<div id="circle"></div>
|
||||
<div id="container">
|
||||
<div id="logo"></div>
|
||||
<div id="form">
|
||||
<input id="pin" type="text" maxlength="7" placeholder="PIN">
|
||||
<div id="names">
|
||||
<select id="naming">
|
||||
<option value="enumerated">Enumerated</option>
|
||||
<option value="capitalized">Randomly capitalized</option>
|
||||
<option value="random">Fully random</option>
|
||||
</select>
|
||||
<input id="name" type="text" maxlength="15" placeholder="Name">
|
||||
</div>
|
||||
<input id="amount" type="text" placeholder="Amount">
|
||||
<input id="flood" type="button" value="Flood">
|
||||
</div>
|
||||
<p id="text"></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Submodule lib/uWebSockets deleted from 84941b99bf
1
lib/zlib
1
lib/zlib
Submodule lib/zlib deleted from 0f51fb4933
@ -1,6 +0,0 @@
|
||||
#include "ClientApp.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
return 0;
|
||||
}
|
Reference in New Issue
Block a user