Solvuria-Backup-For-Gamers/Solvuria.py
2024-04-12 01:07:04 +01:00

540 lines
18 KiB
Python

import hashlib
import json
import random
import secrets
import sys
import time
from datetime import datetime
from getpass import getpass
import requests
# imports msvcrt for windows, or getch for linux
# and sets the correct enter keycode for each os
enterKeyCode = 13
try:
from msvcrt import getch
except ImportError:
from getch import getch
enterKeyCode = 10
VERSION = 142
UserIdentifier = None
UserAgent = None
AuthToken = None
UserData = None
random.seed(secrets.randbelow(1 << 64))
def GetPasswordInput():
# Taken and modified from the pwinput python library
# https://github.com/asweigart/pwinput
enteredPassword = []
sys.stdout.write("[>] Enter your password: ")
sys.stdout.flush()
while True:
key = ord(getch())
if key == enterKeyCode:
sys.stdout.write("\n")
return "".join(enteredPassword)
elif key in (8, 127):
if len(enteredPassword) > 0:
sys.stdout.write("\b \b")
sys.stdout.flush()
enteredPassword = enteredPassword[:-1]
elif 0 <= key <= 31:
pass
else:
char = chr(key)
sys.stdout.write("*")
sys.stdout.flush()
enteredPassword.append(char)
def GetAnswer(answers: list[str], percentageCorrect: int):
def AnswerCorrect(answer: str):
return not int(str(int(answer, 16) * 8779302863457884 % 9007199254740991)[-1])
if secrets.randbelow(100) >= percentageCorrect:
for answer in answers:
if AnswerCorrect(answer):
answers.remove(answer)
break
return [secrets.choice(answers), "Incorrect"] # Return incorrect answer
for answer in answers:
if AnswerCorrect(answer):
return [answer, "Correct"] # Return correct answer
# Answer should have been returned by now; line below is added as a failsafe
return [secrets.choice(answers), "Incorrect"]
def GenerateContentIdentifier(answer_id: str, question_id: str):
return hashlib.sha256(
(question_id + answer_id + UserIdentifier).encode()
).hexdigest()
def GetUserAgent():
return secrets.choice(
[
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.3",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.112 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.3",
"Mozilla/5.0 (Windows NT 10.0; rv:124.0) Gecko/20100101 Firefox/124.0",
]
)
def Authenticate(email: str, password: str):
try:
global UserData, AuthToken, UserIdentifier
Response = requests.post(
"https://kolin.tassomai.com/api/user/login/",
headers={
"accept": "application/json; version=1.18",
"accept-language": "en-GB,en-US;q=0.9,en;q=0.8",
"content-type": "application/json",
"priority": "u=1, i",
"sec-ch-ua": '"Not(A:Brand";v="24", "Chromium";v="122"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site",
"origin": "https://app.tassomai.com/",
"referer": "https://app.tassomai.com/",
"User-Agent": UserAgent,
},
data=json.dumps(
{
"capabilities": {
"cordovaPlatform": None,
"image": True,
"isMobile": False,
"mathjax": True,
"wondeReady": True,
},
"email": email,
"password": password,
}
),
).json()
UserData = Response["user"]
AuthToken = "Bearer " + Response["token"]
UserIdentifier = str(UserData["id"])
return True
except Exception as ex:
return False
################################################################################################################################################################
def GetQuizzes(SubjectId: str):
return requests.get(
"https://kolin.tassomai.com/api/quiz/next/"
+ SubjectId
+ "/?capabilities=%7B%22image%22:true,%22mathjax%22:true,%22isMobile%22:false,%22cordovaPlatform%22:null,%22wondeReady%22:true%7D",
headers={
"accept": "application/json; version=1.18",
"accept-language": "en-GB,en-US;q=0.9,en;q=0.8",
"authorization": AuthToken,
"content-type": "application/json",
"priority": "u=1, i",
"sec-ch-ua": '"Not(A:Brand";v="24", "Chromium";v="122"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site",
"origin": "https://app.tassomai.com/",
"referer": "https://app.tassomai.com/",
"User-Agent": UserAgent,
},
).json()["quizzes"]
def FetchQuizData(courseId: str, playlistId: str):
return requests.post(
"https://kolin.tassomai.com/api/quiz/",
headers={
"accept": "application/json; version=1.18",
"accept-language": "en-GB,en-US;q=0.9,en;q=0.8",
"authorization": AuthToken,
"content-type": "application/json",
"priority": "u=1, i",
"sec-ch-ua": '"Not(A:Brand";v="24", "Chromium";v="122"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site",
"origin": "https://app.tassomai.com/",
"referer": "https://app.tassomai.com/",
"User-Agent": UserAgent,
},
data=json.dumps(
{
"capabilities": {
"cordovaPlatform": None,
"image": True,
"isMobile": False,
"mathjax": True,
"wondeReady": True,
},
"course": courseId,
"playlist": playlistId,
"requestTime": int(time.time_ns() / 1000000),
"was_recommended": True,
}
),
).json()
def AnswerQuestion(answerId: str, askingId: str):
r = requests.post(
"https://kolin.tassomai.com/api/answer/" + str(asking_id) + "/",
headers={
"accept": "application/json; version=1.18",
"accept-language": "en-GB,en-US;q=0.9,en;q=0.8",
"authorization": AuthToken,
"content-type": "application/json",
"priority": "u=1, i",
"sec-ch-ua": '"Not(A:Brand";v="24", "Chromium";v="122"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site",
"origin": "https://app.tassomai.com/",
"referer": "https://app.tassomai.com/",
"User-Agent": UserAgent,
},
data=json.dumps(
{
"answer_id": answerId,
"content_id": GenerateContentIdentifier(answerId, askingId),
"requestTime": int(time.time_ns() / 1000000),
}
),
)
return r.status_code == 200
def GetSubjectList():
return requests.get(
"https://kolin.tassomai.com/api/user/extra/",
headers={
"accept": "application/json; version=1.18",
"accept-language": "en-GB,en-US;q=0.9,en;q=0.8",
"authorization": AuthToken,
"content-type": "application/json",
"priority": "u=1, i",
"sec-ch-ua": '"Not(A:Brand";v="24", "Chromium";v="122"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site",
"origin": "https://app.tassomai.com/",
"referer": "https://app.tassomai.com/",
"User-Agent": UserAgent,
},
).json()["extra"]["currentDisciplines"]
def CaptchaBypass(quiz_data: dict):
if quiz_data["turnstile_mode"] == None:
return [False, quiz_data]
return [
True,
requests.post(
"https://kolin.tassomai.com/api/quiz/fetch/" + str(quiz_data["quiz_id"]),
headers={
"accept": "application/json; version=1.18",
"accept-language": "en-GB,en-US;q=0.9,en;q=0.8",
"authorization": AuthToken,
"content-type": "application/json",
"priority": "u=1, i",
"sec-ch-ua": '"Not(A:Brand";v="24", "Chromium";v="122"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site",
"origin": "https://app.tassomai.com/",
"referer": "https://app.tassomai.com/",
"User-Agent": UserAgent,
},
).json(),
]
def UpdateLastLogin():
currentTime = datetime.now().isoformat()
payload = {"lastLogin": currentTime}
response = requests.post(
"https://kolin.tassomai.com/api/user/extra/",
headers={
"accept": "application/json; version=1.18",
"accept-language": "en-GB,en-US;q=0.9,en;q=0.8",
"authorization": AuthToken,
"content-type": "application/json",
"priority": "u=1, i",
"sec-ch-ua": '"Not(A:Brand";v="24", "Chromium";v="122"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site",
"origin": "https://app.tassomai.com/",
"referer": "https://app.tassomai.com/",
"User-Agent": UserAgent,
},
data=json.dumps(payload),
)
if response.status_code == 405:
return True, currentTime
else:
return False, None
def RefreshToken():
global AuthToken
response = requests.post(
"https://kolin.tassomai.com/api/user/token-refresh/",
headers={
"accept": "application/json; version=1.18",
"accept-language": "en-GB,en-US;q=0.9,en;q=0.8",
"authorization": AuthToken,
"content-type": "application/json",
"priority": "u=1, i",
"sec-ch-ua": '"Not(A:Brand";v="24", "Chromium";v="122"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site",
"origin": "https://app.tassomai.com/",
"referer": "https://app.tassomai.com/",
"User-Agent": UserAgent,
},
data=json.dumps(
{
"token": AuthToken[7:],
}
),
)
newToken = "Bearer " + response.json()["token"]
if newToken == AuthToken and response.status_code == 200:
return False, True
elif newToken != AuthToken and response.status_code == 200:
AuthToken = newToken
return True, True
else:
return False, False
################################################################################################################################################################
UserAgent = GetUserAgent()
print("[>] Solvuria - Enhance your learning experience")
latest = requests.get(
"https://api.github.com/repos/corey-truscott/Solvuria/releases/latest"
).json()
versionName = latest["tag_name"]
if int(versionName.replace(".", "").replace("v", "")) > VERSION:
print(
"[+] Newer release of solvuria is available on github: "
+ "v"
+ versionName
+ " > v"
+ ".".join(list(str(VERSION)))
+ "\n Proceeding regardless may result in bans\n"
)
print("[~] https://github.com/corey-truscott/Solvuria \n " + ("-" * 42 + "\n"))
email = input("[>] Enter your email for signing into tassomai: ")
passw = GetPasswordInput()
if not Authenticate(email, passw):
print(
"[-] Failed to sign into tassomai, please rerun the program and check your credentials are valid!"
)
time.sleep(5)
sys.exit()
lastLogin, currentTime = UpdateLastLogin()
if not lastLogin:
print("[!] Failed to update last login time")
time.sleep(5)
sys.exit()
print(f"[+] Updated last login time: {currentTime}")
changedAuthToken, correctStatusCode = RefreshToken()
if not correctStatusCode:
print("[!] Failed to check token")
time.sleep(5)
sys.exit()
elif not changedAuthToken:
print("[+] Didnt refresh token")
elif changedAuthToken:
print("[+] Refreshed token")
print('[@] Logged in as "' + UserData["firstName"] + " " + UserData["lastName"] + '"\n')
subjectId = ""
subjectList = GetSubjectList()
if len(subjectList) == 1:
subjectId = subjectList[0]["id"]
print(
"[>] Selected "
+ subjectList[0]["name"].lower()
+ " as it is the only available subject!"
)
else:
i = 0
for subject in subjectList:
i += 1
print("[" + str(i) + "] Found subject with name: " + subject["name"])
choice = (
int(
input(
"[>] Enter the number next to the subject which you would like to do: "
)
)
- 1
)
subjectId = subjectList[choice]["id"]
print("[>] Selected " + subjectList[choice]["name"].lower() + "!")
maxDelayBetweenQuestions = None
minDelayBetweenQuestions = None
maxDelayBetweenQuizzes = None
minDelayBetweenQuizzes = None
percentageCorrect = None
stopAfterTime = None
try:
with open("preset.json", "r") as f:
j = json.load(f)
if j["should_use_preset"]:
maxDelayBetweenQuestions = j["maximum_delay_between_questions"]
minDelayBetweenQuestions = j["minimum_delay_between_questions"]
maxDelayBetweenQuizzes = j["maximum_delay_between_quizzes"] / 3
minDelayBetweenQuizzes = j["minimum_delay_between_quizzes"] / 3
percentageCorrect = j["percentage_to_answer_correctly"]
stopAfterTime = j["stop_after_time"] * 60
print("\n[+] Loaded settings from preset.json file")
getpass("[>] Press enter to start: ")
else:
raise Exception("NoPreset")
except:
maxDelayBetweenQuestions = float(
input("\n[>] Maximum delay between questions (seconds): ")
)
minDelayBetweenQuestions = float(
input("[>] Minimum delay between questions (seconds): ")
)
maxDelayBetweenQuizzes = (
float(input("[>] Maximum delay between quizzes (seconds): ")) / 3
) # These are divided by 3 because they are split into multiple different sleeps
minDelayBetweenQuizzes = (
float(input("[>] Minimum delay between quizzes (seconds): ")) / 3
) # The sleeps happen between fetching the quiz and answering the first question
percentageCorrect = int(input("[>] Percentage of questions to answer correctly: "))
stopAfterTime = int(input("[>] Stop after N minutes (0 for infinite): ")) * 60
if percentageCorrect > 100 or percentageCorrect < 0:
percentageCorrect = 80 + secrets.randbelow(6)
if stopAfterTime:
stopAfterTime += secrets.randbelow(20)
firstTimeMeasure = time.time()
while True:
quizzes = GetQuizzes(subjectId)
quiz = False
for q in quizzes:
if q["type"] == "normal":
quiz = q
if quiz == False:
print("[-] Could not find any quizzes to complete!")
print(
"[-] If you have a class task active, you must complete it manually before using this tool"
)
time.sleep(60)
sys.exit()
time.sleep(random.uniform(minDelayBetweenQuizzes, maxDelayBetweenQuizzes))
courseId, playlistId = quiz["courseId"], quiz["playlistId"]
q = time.time()
print(
'\n[>] Completing quiz on "'
+ quiz["playlistName"]
+ '" ('
+ str(courseId)
+ "-"
+ str(playlistId)
+ ")"
)
quizData = FetchQuizData(courseId, playlistId)
time.sleep(random.uniform(minDelayBetweenQuizzes, maxDelayBetweenQuizzes))
quizData = CaptchaBypass(quizData)
if quizData[0]:
print(
"[+] Successfully bypassed captcha for quiz " + str(quizData[1]["quiz_id"])
)
questions = quizData[1]["questions"]
time.sleep(random.uniform(minDelayBetweenQuizzes, maxDelayBetweenQuizzes))
print("[>] Solving questions...\n")
for question in questions:
asking_id = question["asking_id"]
answer_ids = [ans["id"] for ans in question["answers"]]
answer = GetAnswer(answer_ids, percentageCorrect)
time.sleep(random.uniform(minDelayBetweenQuestions, maxDelayBetweenQuestions))
if AnswerQuestion(answer[0], str(asking_id)):
print(
"[+] Answered question "
+ answer[1].lower()
+ 'ly, selected answer is "'
+ answer[0]
+ '"'
)
else:
print("[-] Failed to answer question, sleeping 20 seconds!")
time.sleep(20)
if stopAfterTime:
if time.time() - firstTimeMeasure > stopAfterTime:
print(
"[!] The program will now close as "
+ str(int(stopAfterTime / 60))
+ " minute(s) have passed!"
)
break
print("[+] Completed quiz in " + str(int(time.time() - q)) + " seconds!")
time.sleep(60)
sys.exit()