Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/clientuser #13

Merged
merged 6 commits into from
Nov 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tryhackme/badge.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
class Badge:
def __init__(self, state, data):
self._state = state

self.earned = False
self._from_data(data)

def _from_data(self, data):
Expand Down
23 changes: 15 additions & 8 deletions tryhackme/checks.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,34 @@

from .errors import *

class _base_check:
@property
def name(self):
return type(self).__name__.split('_')[1]

class _type_convertor:

class _type_check(_base_check):
_TYPES = ()
_DEFAULT = None
def convert(self, cls, arg):
if arg is None: return self._DEFAULT
if arg in self._TYPES: return arg
else: raise TypeNotInTypeList(f"'{str(arg)}' is an invalid {self.name} type.")

@property
def name(self):
return type(self).__name__.split('_')[1]

class _vpn_types(_type_convertor):
class _notNone_check(_base_check):
def convert(self, cls, arg):
if arg is not None:
return arg
else: raise NotValidUrlParameters(f"'{str(arg)}' is not allowed to be null {self.name} type.")

class _vpn_types(_type_check):
_TYPES = ('machines', 'networks')
_DEFAULT = 'machines'

class _county_types(_type_convertor):
class _county_types(_type_check):
_TYPES = ("AF","AX","AL","DZ","AS","AD","AO","AI","AQ","AG","AR","AM","AW","AU","AT","AZ","BS","BH","BD","BB","BY","BE","BZ","BJ","BM","BT","BO","BQ","BA","BW","BV","BR","IO","BN","BG","BF","BI","KH","CM","CA","CV","KY","CF","TD","CL","CN","CX","CC","CO","KM","CG","CD","CK","CR","CI","HR","CU","CW","CY","CZ","DK","DJ","DM","DO","EC","EG","SV","GQ","ER","EE","ET","FK","FO","FJ","FI","FR","GF","PF","TF","GA","GM","GE","DE","GH","GI","GR","GL","GD","GP","GU","GT","GG","GN","GW","GY","HT","HM","VA","HN","HK","HU","IS","IN","ID","IR","IQ","IE","IM","IL","IT","JM","JP","JE","JO","KZ","KE","KI","KP","KR","KW","KG","LA","LV","LB","LS","LR","LY","LI","LT","LU","MO","MK","MG","MW","MY","MV","ML","MT","MH","MQ","MR","MU","YT","MX","FM","MD","MC","MN","ME","MS","MA","MZ","MM","NA","NR","NP","NL","NC","NZ","NI","NE","NG","NU","NF","MP","NO","OM","PK","PW","PS","PA","PG","PY","PE","PH","PN","PL","PT","PR","QA","RE","RO","RU","RW","BL","SH","KN","LC","MF","PM","VC","WS","SM","ST","SA","SN","RS","SC","SL","SG","SX","SK","SI","SB","SO","ZA","GS","SS","ES","LK","SD","SR","SJ","SZ","SE","CH","SY","TW","TJ","TZ","TH","TL","TG","TK","TO","TT","TN","TR","TM","TC","TV","UG","UA","AE","GB","US","UM","UY","UZ","VU","VE","VN","VG","VI","WF","EH","YE","ZM","ZW")

class _leaderboard_types(_type_convertor):
class _leaderboard_types(_type_check):
_TYPES = ("monthly", "")


Expand Down
25 changes: 17 additions & 8 deletions tryhackme/client.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from .http import HTTP
from .state import State
from .user import ClientUser


# TODO: build out Hackivities (room search), HTML scrapper for username and CSRF token, error build out, de HTML question object
# TODO: build out Hackivities (room search) error build out
# TODO: add VM, add GAMES, add VPN, user: (Team, messages, notifications)
# ? maybe a writeup class but maybe not

Expand All @@ -13,6 +14,12 @@ def __init__(self, session=None):

def login(self, session):
self.http.static_login(session)
if self._state.authenticated:
try:
self._state.user = ClientUser(state=self._state, username=self.http.username)
except Exception as e:
print("Failed to create CLient user: ", str(e))
self._state.authenticated = False

def get_room(self, room_code):
try:
Expand Down Expand Up @@ -40,18 +47,17 @@ def get_user(self, username):

def get_badge(self, badge_name):
return self._state.get_badge(badge_name)

def get_badges(self):
return self._state.badges

def get_practice_rooms(self):
practice_rooms = self.http.get_practise_rooms()
return_rooms = []
return_rooms += [self._state.store_room(data=room) for room in practice_rooms.get("featured", [])]
return_rooms += [self._state.store_room(data=room) for room in practice_rooms.get("webExploitation", [])]
return_rooms += [self._state.store_room(data=room) for room in practice_rooms.get("windowsExploitation", [])]
return_rooms += [self._state.store_room(data=room) for room in practice_rooms.get("defensive", [])]
return_rooms += [self._state.store_room(data=room) for room in practice_rooms.get("recommended", [])]
return_rooms += [self._state.get_room(room_code=room.get("code")) for room in practice_rooms.get("featured", [])]
return_rooms += [self._state.get_room(room_code=room.get("code")) for room in practice_rooms.get("webExploitation", [])]
return_rooms += [self._state.get_room(room_code=room.get("code")) for room in practice_rooms.get("windowsExploitation", [])]
return_rooms += [self._state.get_room(room_code=room.get("code")) for room in practice_rooms.get("defensive", [])]
return_rooms += [self._state.get_room(room_code=room.get("code")) for room in practice_rooms.get("recommended", [])]
return return_rooms

# ! network is basicly nothing at the moment since i cant access is (im not a premium member)
Expand Down Expand Up @@ -100,4 +106,7 @@ def glossary(self):
return self.http.get_glossary_terms()
@property
def user(self):
return self._state.get_client_user()
return self._state.user
@property
def authenticated(self):
return self._state.authenticated
2 changes: 1 addition & 1 deletion tryhackme/cog.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def convert(self, *args, **kwargs):
if arg in annotions:
try:
result = self.function.__annotations__[arg]
out_kwargs[arg] = result().convert(kwargs[arg])
out_kwargs[arg] = result().convert(self.cls, kwargs[arg])
except Exception as e:
raise e
else: out_kwargs[arg] = kwargs[arg]
Expand Down
2 changes: 1 addition & 1 deletion tryhackme/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def __init__(self, request, route, data):
self.data = data

def __str__(self):
return f"{type(self).__name__}(code={self.request.status_code}, return_URL={self.route.url}, data_length: {self.data.__len__()})"
return f"{type(self).__name__}(code={self.request.status_code}, URL={self.route.path}, returned_url={self.request.url}, data_length: {self.data.__len__()})"

class Unauthorized(WebError):
pass
Expand Down
60 changes: 37 additions & 23 deletions tryhackme/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import requests

from . import __version__, errors, utils
from .checks import _county_types, _leaderboard_types, _vpn_types
from .checks import _county_types, _leaderboard_types, _vpn_types, _notNone_check
from .cog import request_cog

GET='get'
Expand All @@ -14,14 +14,14 @@

class HTTPClient:
__CSRF_token_regex = re.compile("const csrfToken[ ]{0,1}=[ ]{0,1}[\"|'](.{36})[\"|']")
__Username_regex = re.compile("const username[ ]{0,1}=[ ]{0,1}[\"|'](.{16})[\"|']")
__Username_regex = re.compile("const username[ ]{0,1}=[ ]{0,1}[\"|'](.{1,16})[\"|']")
def __init__(self, session=None):
self.authenticated = False
self.__session = requests.Session()
self.satic_session = requests.Session()
self.connect_sid = None
self._CSRF_token = None
self._username = None
self.username = None

self.user_agent = f'Tryhackme: (https://github.com/GnarLito/thm-api-py {__version__}) Python/{sys.version_info[0]}.{sys.version_info[1]} requests/{requests.__version__}'

Expand All @@ -39,21 +39,30 @@ def static_login(self, session):
try:
self.request(RouteList.get_unseen_notifications())
self.authenticated = True
self.retrieve_CSRF_token()
self.retrieve_username()
except: pass
self._CSRF_token = self.retrieve_CSRF_token()
self.username = self.retrieve_username()
except Exception as e:
print("session Issue:", e)

def retrieve_CSRF_token(self):
if not self.authenticated:
return
page = self.request(RouteList.get_profile_page())
self._CSRF_token = self.__CSRF_token_regex.search(page).group(1)
return None
try:
page = self.request(RouteList.get_profile_page())
return self._HTTPClient__CSRF_token_regex.search(page).group(1)
except AttributeError:
self.authenticated = False
return None

def retrieve_username(self):
if not self.authenticated:
return
page = self.request(RouteList.get_profile_page())
self.username = self.__Username_regex.search(page).group(1)
return None
try:
page = self.request(RouteList.get_profile_page())
return self._HTTPClient__Username_regex.search(page).group(1)
except AttributeError:
self.authenticated = False
return None

def request(self, route, **kwargs):
session = self.__session
Expand Down Expand Up @@ -101,21 +110,23 @@ def request(self, route, **kwargs):
raise errors.ServerError(request=r, route=route, data=data)

except Exception as e:
print(e)
raise e


class Route:
# TODO: add post payload capabilities
BASE = "https://www.tryhackme.com"
def __init__(self, method=GET, path='', **parameters):
self.method = method
self._path = path
self.path = path
url = self.BASE + self.path

options = parameters.pop("options", None)
if parameters:
try:
self.url = url.format(**{k: _uriquote(v) if isinstance(v, str) else v for k, v in parameters.items()})
self.path = self.path.format(**{k: _uriquote(v) if isinstance(v, str) else v for k, v in parameters.items()})
self.url = self.BASE + self.path
except Exception as e:
raise errors.NotValidUrlParameters(e)
else:
Expand Down Expand Up @@ -200,13 +211,14 @@ def get_team_info(**parameters): return Route(path="/api/team/is-member", **para

# * user -notifications

def get_unseen_notifications(**parameters): return Route(path="/api/notifications/has-unseen", **parameters)
def get_all_notifications( **parameters): return Route(path="/api/notifications/get", **parameters)
def get_unseen_notifications(**parameters): return Route(path="/notifications/has-unseen", **parameters)
def get_all_notifications( **parameters): return Route(path="/notifications/get", **parameters)

# * user -messages

def get_unseen_messages( **parameters): return Route(path="/api/message/has-unseen", **parameters)
def get_all_group_messages(**parameters): return Route(path="/api/message/group/get-all", **parameters)
def get_unseen_messages( **parameters): return Route(path="/message/has-unseen", **parameters)
def get_all_group_messages(**parameters): return Route(path="/message/group/get-all", **parameters)
def get_group_messages( **parameters): return Route(path="/message/group/get/{group_id}", **parameters)

# * user -room

Expand Down Expand Up @@ -353,6 +365,8 @@ def get_unseen_messages(self):
return self.request(RouteList.get_unseen_messages())
def get_all_group_messages(self):
return self.request(RouteList.get_all_group_messages())
def get_group_messages(self, group_id):
return self.request(RouteList.get_group_messages(group_id))

# * user -room

Expand All @@ -365,17 +379,17 @@ def get_user_created_rooms(self, username, limit:int=10, page:int=1):

# * user

def get_user_rank(self, username):
def get_user_rank(self, username : _notNone_check):
return self.request(RouteList.get_user_rank(username=username))
def get_user_activty(self, username):
def get_user_activty(self, username : _notNone_check):
return self.request(RouteList.get_user_activty(username=username))
def get_all_friends(self):
return self.request(RouteList.get_all_friends())
def get_discord_user(self, username):
def get_discord_user(self, username : _notNone_check):
return self.request(RouteList.get_discord_user(username=username))
def get_user_exist(self, username):
def get_user_exist(self, username : _notNone_check):
return self.request(RouteList.get_user_exist(username=username))
def search_user(self, username):
def search_user(self, username : _notNone_check):
return self.request(RouteList.search_user(username=username))

# * room
Expand Down
41 changes: 41 additions & 0 deletions tryhackme/message.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from . import utils

class Message:
def __init__(self, state, group, data):
self._state = state
self.group = group
self._from_data(data)

def _from_data(self, data):
self.message = data.get("message")
self.inserted = data.get("inserted")
self.user = self.group.get_user_from_userId(data.get("userId"))


# * can only be used on `get_all_message_groups` api call
class MessageGroup:
def __init__(self, state, data):
self._state = state
self.messages = []
self._from_data(data)

def _from_data(self, data):
self.id = data.get("groupId")
self.title = data.get("title")
self._users = data.get("users")

self._sync(data)

def _sync(self, data):
self.messages = [Message(state=self._state, group=self, data=message) for message in self._state.http.get_group_messages(self.id)]

@property
def users(self):
return [self._stats.store_user(user) for user in self._users]

def get_user_from_userId(self, userId):
try:
username = [user.get("username") for user in self._users if user.get("userId") == userId]
return self._state.get_user(username[0])
except:
return None
Empty file added tryhackme/notifications.py
Empty file.
19 changes: 7 additions & 12 deletions tryhackme/question.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,23 @@
from . import utils

# TODO: de HTML all the things
class Question:
def __init__(self, state, data):
self._state = state
self._from_data(data)

def _from_data(self, data):
self.raw_question = data.get("question")
self.number = data.get("questionNo")
self.question = utils.HTML_parse(self.raw_question)
self.raw_hint = data.get("hint")
self.hint = utils.HTML_parse(self.raw_hint)
self.number = data.get("questionNo")

# * only when valid session is used
self.raw_description = data.get("answerDesc", "")
self.description = utils.HTML_parse(self.raw_description)
self.extra_points = data.get("extraPoints", None)
self.correct = data.get("correct", False)
self.attempts = data.get("attempts", 0)
self.submission = data.get("submission", "")
self.has_answer = data.get("noAnswer", False)

@property
def question(self):
return utils.HTML_parse(self.raw_question)
@property
def description(self):
return utils.HTML_parse(self.raw_description)
@property
def hint(self):
return utils.HTML_parse(self.raw_hint)

6 changes: 4 additions & 2 deletions tryhackme/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ def __init__(self, state, data):
self._creators = []

if not data.get('success', False):
raise NotImplemented("failed to create room, no success value returned")
if data.get("code") == 5:
raise NotImplemented(f"Room: {data.get('roomCode')}, Unable to load room: {data.get('message')}")
else:
raise NotImplemented("failed to create room, no success value returned")

self._from_data(data)

Expand Down Expand Up @@ -57,7 +60,6 @@ def scoreboard(self):
return self._state.http.get_room_scoreboard(room_code=self.name)
@property
def tasks(self):
# TODO: add sessionless http client for no session task gathering
if self.freeToUse or self._state.subscribed:
return [RoomTask(state=self._state, data=task) for task in self._state.http.get_room_tasks(room_code=self.name).get('data')]
else:
Expand Down
Loading