diff --git a/README-selenium.md b/README-selenium.md new file mode 100644 index 0000000..f43acb4 --- /dev/null +++ b/README-selenium.md @@ -0,0 +1,43 @@ +# Kahoot God (Kahoot Answer Bot) + +Fork of: https://github.com/The-CodingSloth/kahoot-god + +## Description + +This project scrapes a Kahoot quiz page, extracts the question and answer choices, and sends them to a local LLM via Ollama for prediction. The model returns the most likely correct answer (0–3 index). + +The current version outputs the selected answer to the console. Automated clicking support is planned for future updates. + +## How it works + +1. Opens Kahoot quiz page +2. Scrapes question + answers using Selenium +3. Sends data to an Ollama model +4. Prints predicted answer index (0–3) + +## Prerequisites + +- Python 3.10+ +- Ollama installed and running +- A compatible model (e.g. llama3, mistral, etc.) +- Required Python packages: + - selenium + - requests + - pyautogui + - pyperclip + - colorama + +## Notes + +- This project depends on DOM structure and may break if the page layout changes. +- Model accuracy depends heavily on the selected LLM. +- The script may open a new Chrome window. This is expected and is used to scrape the page. +- This repository is a fork of the original project. It has been modified and tuned for additional functionality and is redistributed under the MIT License. + +## Credits + +Original project: https://github.com/The-CodingSloth/kahoot-god + +--- +#### Result of using a model with less parameters +[Recording 2026-05-21 105818.mp4](Recording%202026-05-21%20105818.mp4) \ No newline at end of file diff --git a/kahoot_god_selenium.py b/kahoot_god_selenium.py new file mode 100644 index 0000000..ab68805 --- /dev/null +++ b/kahoot_god_selenium.py @@ -0,0 +1,270 @@ +# ==================== +# CONFIG +# ==================== + +import time +import requests +import pyautogui +import pyperclip +from colorama import init, Fore +import keyboard +import openai + + +init() + +RED = Fore.RED +GREEN = Fore.GREEN +YELLOW = Fore.YELLOW +CYAN = Fore.CYAN +BLUE = Fore.BLUE + +provider = input(CYAN + "[1] Ollama\n[2] OpenAi\n[+]\nPlease choose your provider:") +usingOpenai = False + +match(provider): + case "1": + usingOpenai = False + case "2": + + usingOpenai = True + case _: + print(RED + "[!] Invalid provider") + + +match(usingOpenai): + case True: + selected_model = input(BLUE + "[!] Please choose your OpenAi model: ").lower() # You can change to your actual model if you are lazy and don't want to enter the model again and again + case False: + selected_model = input(BLUE + "[!] Please choose your model: ").lower() + +HOTKEY = "ctrl+b" +WAIT_BEFORE_CAPTURE = 3 + +def chooseBrowser(): + usingBrowsers = [] + which_browser = None + a = input(BLUE + "[?] Do you have Chrome (True/False or T/F or 0/1): ") + usingChrome = True + if a.lower() == "f" or a.lower() == "false" or a.lower() == "0": + usingChrome = False + if 'chrome' in usingBrowsers: + usingBrowsers.remove('chrome') + + elif a.lower() == "t" or a.lower() == "true" or a.lower() == "1": + usingChrome = True + if 'chrome' not in usingBrowsers: + usingBrowsers.append('chrome') + + b = input(BLUE + "[?] Do you have Edge (True/False or T/F or 0/1): ") + usingEdge = False + if b.lower() == "f" or b.lower() == "false" or b.lower() == "0": + usingEdge = False + if 'edge' in usingBrowsers: + usingBrowsers.remove('edge') + + elif b.lower() == "t" or b.lower() == "true" or b.lower() == "1": + usingEdge = True + if 'edge' not in usingBrowsers: + usingBrowsers.append('edge') + + c = input(BLUE + "[?] Do you have Firefox (True/False or T/F or 0/1): ") + usingFirefox = False + if c.lower() == "f" or c.lower() == "false" or c.lower() == "0": + usingFirefox = False + if 'firefox' in usingBrowsers: + usingBrowsers.remove('firefox') + + elif c.lower() == "t" or c.lower() == "true" or c.lower() == "1": + usingFirefox = True + if 'firefox' not in usingBrowsers: + usingBrowsers.append('firefox') + + d = input(BLUE + "[?] Do you have Chromium (True/False or T/F or 0/1): ") + usingChromium = False + if d.lower() == "f" or d.lower() == "false" or d.lower() == "0": + usingChromium = False + if 'chrome' in usingChrome: + usingBrowsers.remove('chromium') + + elif d.lower() == "t" or d.lower() == "true" or d.lower() == "1": + usingChromium = True + if 'chromium' not in usingChromium: + usingBrowsers.append('chromium') + + e = input(BLUE + "[?] Do you have Safari (True/False or T/F or 0/1): ") + usingSafari = False + if e.lower() == "f" or e.lower() == "false" or e.lower() == "0": + usingSafari = False + if 'safari' in usingBrowsers: + usingBrowsers.remove('safari') + + elif e.lower() == "t" or e.lower() == "true" or e.lower() == "1": + usingSafari = True + if 'safari' not in usingBrowsers: + usingBrowsers.append('safari') + + + if len(usingBrowsers) > 1: + for i, browser in enumerate(usingBrowsers): + print(CYAN + f"[{i+1}] {browser}") + which_browser = input(BLUE + f"[+] Which browser you want to use: ") + while which_browser not in usingBrowsers: + print(RED + "[!] Invalid browser") + which_browser = input(YELLOW + f"[+] Try again: ") + print(GREEN + f"[✓] Browser Chosen: {which_browser}") + + elif len(usingBrowsers) == 1: + which_browser = usingBrowsers[0] + + else: + print(RED + "[!] Does this bro seriously needs TOR just for playing some Kahoot??") + + return which_browser + +from selenium import webdriver +from selenium.webdriver.common.by import By + +def _getOllamaModel(model_list_url): + res = requests.get(model_list_url).json() + model_list = [model["name"].lower() for model in res["models"]] + + return model_list + +def useOllama(question, answers): + OLLAMA_BASE_URL = "http://localhost:11434" + OLLAMA_MODEL_LIST_URL = f"{OLLAMA_BASE_URL}/api/tags" + OLLAMA_CHAT_URL = f"{OLLAMA_BASE_URL}/api/chat" + + model_list = _getOllamaModel(OLLAMA_MODEL_LIST_URL) + for i, model in enumerate(model_list): + print(CYAN + f"[{i+1}] {model}") + + while selected_model not in model_list: + print(RED + "[!] Invalid model") + model = input(Fore.MAGENTA + "[!] Please try entering the model name again: ").lower() + + print(GREEN + "[✓] Model Chosen: " + selected_model) + + system_prompt = """ + You are a multiple-choice classifier. + + Return ONLY 0, 1, 2, or more positive integers.. + + No words. No explanation. + IF NO OPTIONS ARE PROVIDED. Make an educational guess. + """ + + user_prompt = f""" + Question: {question} + Answers: {answers} + """ + + payload = { + "model": selected_model, + "messages": [ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_prompt} + ], + "stream": False + } + try: + res = requests.post(OLLAMA_CHAT_URL, json=payload).json() + reply = res["message"]["content"].strip() + return int(reply), selected_model + except ValueError: + print(RED + "[!] Invalid model response: " + reply) + return int(reply[0]), selected_model + except Exception: + print(RED + "[!] Something went wrong.") + print(YELLOW + res) + return None, selected_model + + + + +def useOpenAi(question, answers): + openai.api_key = "YOUR_OPENAI_API_KEY" + + conversation = [ + { + "role": "system", + "content": "You are a helpful assistant specialized in answering multiple-choice questions who only responds with the button number corresponding to the most likely answer do not respond with words only a integer. You'll do this even if the question involves content you can't analyze, such as videos or images. If you cannot answer the question, you'll respond with an educated guess. Remember only respond with an integer between 1 and 4 that corresponds to the answer.", + }, + {"role": "user", "content": f"Questions: {question}, Answer choices: {answers}"}, + ] + try: + response = openai.ChatCompletion.create(model=selected_model, + messages=conversation) # # can be set to 'gpt-4' if needed (yeah I commented the comment, because I am too lazy to delete it) + + # Extract the assistant's reply + reply = response["choices"][0]["message"]["content"].strip() + # Return the button number as an integer + return int(reply), selected_model + except ValueError: + # Handle exception here if the reply is not an integer + print(f"Unexpected reply: {reply}") + return int(reply[0]), selected_model + except: + # Handle exception here if something else goes wrong + print("Something went wrong.") + print(response) + return None, selected_model + +def get_kahoot_url(): + print(YELLOW + "[!] Open Kahoot browser window") + print(YELLOW + f"[!] Waiting {WAIT_BEFORE_CAPTURE} seconds...") + + time.sleep(WAIT_BEFORE_CAPTURE) + + pyautogui.hotkey('ctrl', 'l') + time.sleep(0.2) + pyautogui.hotkey('ctrl', 'c') + + return pyperclip.paste() + +def scrape_kahoot(driver): + question = driver.find_element( + By.CSS_SELECTOR, + 'span[data-functional-selector="block-title"]' + ).text + + answers = driver.find_elements( + By.CSS_SELECTOR, + 'p[class*="break-long-words"]' + ) + + answer_list = [a.text for a in answers if a.text != ""] + + return question, answer_list + +def run_bot(driver): + question, answers = scrape_kahoot(driver) + + print(GREEN + f"Q: {question}") + print(GREEN + f"A: {answers}") + + if usingOpenai: + answer, model = useOpenAi(question, answers) + elif not usingOpenai: + answer, model = useOllama(question, answers) + + print(YELLOW + f"[{model}] Answer: {answer}") + +def main(): + url = get_kahoot_url() + # ============================== + # Browser Configuration + # ============================== + + driver = webdriver.Chrome() + driver.get(url) + + print(GREEN + "[✓] Bot ready. Press hotkey to answer.") + + keyboard.add_hotkey(HOTKEY, lambda: run_bot(driver)) + keyboard.wait() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/requirements-selenium.txt b/requirements-selenium.txt new file mode 100644 index 0000000..3ea5de5 --- /dev/null +++ b/requirements-selenium.txt @@ -0,0 +1,4 @@ +colorama +pyautogui +pyperclip +selenium