Skip to content

Commit 776d359

Browse files
authored
fix SSL and HTTP 403 issues for the non-enhanced version
1 parent 1eea399 commit 776d359

File tree

1 file changed

+173
-8
lines changed

1 file changed

+173
-8
lines changed

Kitty/client.py

Lines changed: 173 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22
import time
33
from os import system
44
import platform
5+
import json
6+
import re
7+
import ssl
8+
import random
9+
import threading
10+
from urllib.request import urlopen, Request
11+
from urllib.error import HTTPError, URLError
12+
from urllib.parse import urlparse, parse_qs
13+
from http.client import InvalidURL
514

615
print("Attempting to check if imports are installed; colorama, pystyle.")
716
time.sleep(1)
@@ -14,17 +23,17 @@ def clear():
1423
elif system == 'linux' or system == 'darwin':
1524
_ = os.system('clear')
1625
elif system == 'android':
17-
System.Clear()
26+
_ = os.system('clear')
1827
print("How are you here, leave!")
19-
sprint(f"Please use the 'LITE' version so this kahoot client will run smoothly! {r}<3")
28+
print("Please use the 'LITE' version so this kahoot client will run smoothly!")
2029
exit()
2130
clear() # Call clear func
31+
2232
try:
2333
import colorama
2434
import pystyle
2535
except ModuleNotFoundError:
26-
#from scripts.check import try_install | Idk how to fix the error for line 16, its 2am so idc
27-
print("Result: You dont have a certan import(s) installed, installing them now")
36+
print("Result: You dont have a certain import(s) installed, installing them now")
2837
time.sleep(1)
2938
os.system("pip install colorama")
3039
os.system("pip install pystyle")
@@ -58,18 +67,163 @@ def clear():
5867
output_lock = threading.Lock()
5968
colorama.init()
6069

70+
class SSLContextManager:
71+
"""Handle SSL certificate issues across platforms"""
72+
73+
@staticmethod
74+
def create_ssl_context():
75+
"""Create SSL context with fallback for certificate issues"""
76+
try:
77+
# Try to create default context first
78+
context = ssl.create_default_context()
79+
80+
# For macOS, try to use certifi if available
81+
if platform.system() == 'Darwin':
82+
try:
83+
import certifi
84+
context = ssl.create_default_context(cafile=certifi.where())
85+
except ImportError:
86+
# certifi not available, use default
87+
pass
88+
89+
return context
90+
except Exception:
91+
# If all else fails, create unverified context
92+
print("Warning: Using unverified SSL context due to certificate issues")
93+
return ssl._create_unverified_context()
94+
95+
class RateLimiter:
96+
"""Rate limiter to avoid getting blocked"""
97+
98+
def __init__(self, min_delay=1.0, max_delay=3.0):
99+
self.min_delay = min_delay
100+
self.max_delay = max_delay
101+
self.last_request = None
102+
103+
def wait(self):
104+
"""Wait appropriate time between requests"""
105+
if self.last_request:
106+
elapsed = time.time() - self.last_request
107+
delay = random.uniform(self.min_delay, self.max_delay)
108+
109+
if elapsed < delay:
110+
sleep_time = delay - elapsed
111+
time.sleep(sleep_time)
112+
113+
self.last_request = time.time()
114+
115+
# Enhanced Kahoot API
61116
api = "https://play.kahoot.it/rest/kahoots/"
117+
62118
class Kahoot:
63119
def __init__(self, uuid):
64120
self.uuid = uuid
121+
self.rate_limiter = RateLimiter()
122+
self.ssl_context = SSLContextManager.create_ssl_context()
123+
65124
try:
66125
if not re.fullmatch(r"^[A-Za-z0-9-]*$", uuid):
67126
self.data = False
68127
else:
69-
self.data = load(urlopen(f"https://play.kahoot.it/rest/kahoots/{uuid}"))
128+
self.data = self._fetch_quiz_data(uuid)
70129
except HTTPError or InvalidURL:
71130
self.data = False
72131

132+
def _get_headers(self):
133+
"""Generate realistic browser headers"""
134+
user_agents = [
135+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
136+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
137+
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
138+
]
139+
140+
return {
141+
'User-Agent': random.choice(user_agents),
142+
'Accept': 'application/json, text/plain, */*',
143+
'Accept-Language': 'en-US,en;q=0.9',
144+
'Accept-Encoding': 'gzip, deflate, br',
145+
'DNT': '1',
146+
'Connection': 'keep-alive',
147+
'Cache-Control': 'no-cache',
148+
'Pragma': 'no-cache'
149+
}
150+
151+
def _fetch_quiz_data(self, uuid):
152+
"""Fetch quiz data with enhanced error handling"""
153+
max_retries = 3
154+
155+
for attempt in range(max_retries):
156+
try:
157+
# Apply rate limiting
158+
self.rate_limiter.wait()
159+
160+
url = f"https://play.kahoot.it/rest/kahoots/{uuid}"
161+
headers = self._get_headers()
162+
request = Request(url, headers=headers)
163+
164+
with urlopen(request, timeout=15, context=self.ssl_context) as response:
165+
return load(response)
166+
167+
except HTTPError as e:
168+
if e.code == 403:
169+
print(f"Attempt {attempt + 1}: Access forbidden (403)")
170+
if attempt < max_retries - 1:
171+
wait_time = (attempt + 1) * 5
172+
print(f"Waiting {wait_time} seconds before retry...")
173+
time.sleep(wait_time)
174+
continue
175+
else:
176+
print("Error: Access forbidden. This could be due to rate limiting or geographic restrictions.")
177+
return False
178+
179+
elif e.code == 404:
180+
print("Error: Quiz not found. Please verify the Quiz ID is correct.")
181+
return False
182+
183+
elif e.code == 429: # Too Many Requests
184+
if attempt < max_retries - 1:
185+
wait_time = (attempt + 1) * 10
186+
print(f"Rate limited. Waiting {wait_time} seconds...")
187+
time.sleep(wait_time)
188+
continue
189+
else:
190+
print("Error: Rate limited by server. Please wait a few minutes and try again.")
191+
return False
192+
193+
else:
194+
print(f"HTTP Error {e.code}: {e.reason}")
195+
return False
196+
197+
except URLError as e:
198+
print(f"Connection error: {e.reason}")
199+
return False
200+
201+
except ssl.SSLError as e:
202+
print(f"SSL Error on attempt {attempt + 1}: {e}")
203+
if attempt < max_retries - 1:
204+
# Try with unverified context on next attempt
205+
self.ssl_context = ssl._create_unverified_context()
206+
print("Retrying with unverified SSL context...")
207+
continue
208+
else:
209+
print(f"SSL connection failed: {e}")
210+
return False
211+
212+
except json.JSONDecodeError:
213+
print("Error: Failed to parse the response from Kahoot servers.")
214+
return False
215+
216+
except Exception as e:
217+
if attempt < max_retries - 1:
218+
print(f"Attempt {attempt + 1} failed: {e}")
219+
time.sleep(2)
220+
continue
221+
else:
222+
print(f"Unexpected error: {str(e)}")
223+
return False
224+
225+
return False
226+
73227
def get_quiz_details(self):
74228
return {
75229
"uuid": self.data["uuid"],
@@ -153,6 +307,7 @@ def get_answer(self, question):
153307
if len(answers) == 0:
154308
answers = None
155309
return answers
310+
156311
def start_kahoot():
157312
print("NOTICE: This version is under development and sometimes it might bug, please create an issue at 'https://github.com/CPScript/Kitty-tools/issues' and we will try to fix it <3")
158313
Write.Print(f"""
@@ -169,6 +324,12 @@ def start_kahoot():
169324
Write.Print(f"└─────► ", Colors.white, interval=0.000); quiz_id = input(pretty)
170325
try:
171326
kahoot = Kahoot(quiz_id)
327+
328+
if not kahoot.data:
329+
print(f"{red}Error: Failed to fetch quiz data. Please check the Quiz ID and try again.{reset}")
330+
input("Press any key to exit...")
331+
return
332+
172333
print(f"{pretty}{orange}({green}!{orange}) Fetching Answers From: {orange}[{reset}Quiz-ID: {quiz_id}{orange}]\n")
173334
time.sleep(1)
174335
for i in range(kahoot.get_quiz_length()):
@@ -183,8 +344,13 @@ def start_kahoot():
183344
except Exception as err:
184345
os.system('clear')
185346
print("Womp Womp! ")
186-
print("There was an error! Mabey you typed the 'Quiz ID' incorrectly!\n")
187-
print(err)
347+
print("There was an error! Maybe you typed the 'Quiz ID' incorrectly!\n")
348+
print(f"Error details: {err}")
349+
print("\nTroubleshooting tips:")
350+
print("1. Check your internet connection")
351+
print("2. Verify the Quiz ID is correct")
352+
print("3. The quiz might be private or restricted")
353+
print("4. Try again in a few minutes")
188354
Write.Print(f"""
189355
||=========================================================
190356
||Thanks for using Kitty-Tools <3
@@ -194,5 +360,4 @@ def start_kahoot():
194360

195361
start_kahoot()
196362

197-
198363
input("Press any key to exit...")

0 commit comments

Comments
 (0)