diff --git a/htb2trilium2.py b/htb2trilium2.py deleted file mode 100644 index c831574..0000000 --- a/htb2trilium2.py +++ /dev/null @@ -1,224 +0,0 @@ -''' -######################## -# SETUP -######################## - > pip3 install trilium-py - - - get trilium token in trilium options->ETAPI and "create new token" - - get trilium_htb_folder click on note to become folder -> "note info". Note ID is there. - - set the folder's owned attributes to: - #label:user=promoted,single,text #label:root=promoted,single,text #label:respect=promoted,single,text #user=0 #root=0 #respect=0 - - - create a note named "HTBMachineTemplate" and set it's "owned attributes" to: - #template #label:User=promoted,single,text #label:Root=promoted,single,text #label:Tags=promoted,single,text - - - get this page's ID and put in "trilium_template_id" - - to get "in progress" todo colour - add to trilium demo/scripting/taskmanager/implementation/CSS: - - span.fancytree-node.inprogress .fancytree-title { - color: orange !important; - } -''' - -######################## -# IGNORE THESE -######################## - -import requests -from bs4 import BeautifulSoup -from datetime import datetime, timezone -from htb_client import HTBClient -from trilium_py.client import ETAPI - -######################## -# EDIT BELOW HERE -######################## - -htb_code = '' -trilium_server_url = 'https://notes.place.com' -trilium_token = '' -trilium_htb_folder = '' -trilium_template_id = '' - -######################## -# LEAVE BELOW HERE -######################## - -def generate_newpage(machine): - no = "No" - yes = "Yes" - user_colour = no - if machine['authUserInUserOwns']: - user_colour = yes - root_colour = no - if machine['authUserInRootOwns']: - root_colour = yes - - status = "Retired" - if machine['active']: - status = "Active" - - release_str = machine['release'] - release_date = datetime.strptime(release_str, "%Y-%m-%dT%H:%M:%S.%fZ") - formatted_release_date = release_date.strftime("%d %B %Y") - - html = """ -
- -
-

- OS: {os}
- Difficulty: {difficultyText}
- Rating: {rating} / 5
- Points: {points}
- User / Root: {user_colour} / {root_colour}
- Released: {release_date}
- State: {status} -

-
-

Notes

-

 

- """.format( - os=machine['os'], - difficultyText=machine['difficultyText'], - rating=machine['star'], - points=machine['points'], - release_date=formatted_release_date, - user_colour=user_colour, - root_colour=root_colour, - status = status, - avatar = machine['avatar'], - ) - - return html - -def get_timestamp(machine): - # Parse the release date string into a datetime object - release_str = machine['release'] - dt = datetime.strptime(release_str, "%Y-%m-%dT%H:%M:%S.%fZ") - - # Set the timezone to UTC - dt = dt.replace(tzinfo=timezone.utc) - - # Get the timestamp - timestamp = dt.timestamp() - return timestamp - -print("[+] connecting to HTB") -client = HTBClient(password=htb_code) -print("[+] connecting to trilium") -ea = ETAPI(trilium_server_url, trilium_token) -print("[i] version: ", ea.app_info()['appVersion']) - -print("[i] HTB User:", client.user['id'], "-", client.user['name']) -print("[i] user owns:", client.user['user_owns'], "| Root owns:", client.user['root_owns'], "| Respect:", client.user['respects']) - -master_folder = ea.get_note(trilium_htb_folder) -for attribute in master_folder['attributes']: - if attribute['name'] == "user": - if attribute['value'] != str(client.user['user_owns']): - print("[+] updating user owns (folder attribute)") - ea.patch_attribute(attributeId=attribute['attributeId'], value=str(client.user['user_owns'])) - if attribute['name'] == "root": - if attribute['value'] != str(client.user['root_owns']): - print("[+] updating root owns (folder attribute)") - ea.patch_attribute(attributeId=attribute['attributeId'], value=str(client.user['root_owns'])) - if attribute['name'] == "respect": - if attribute['value'] != str(client.user['respects']): - print("[+] updating respect (folder attribute)") - ea.patch_attribute(attributeId=attribute['attributeId'], value=str(client.user['respects'])) - -print("[+] gathering machines info") -machines = client.get_all_machines() -machines.sort(key=get_timestamp) - -print(f"[i] Retrieved {len(machines)} machines") -#for machine in machines: -# print(f" - ID: {machine['id']}, Name: {machine['name']}, OS: {machine['os']}, Difficulty: {machine['difficultyText']}") - -machine_count = 0 -completed_count = 0 -for machine in machines: - machine_count += 1 - print('processing: ',machine_count, "/", len(machines), "("+machine['name']+") " , end='\r') - - res = ea.search_note( - search="\""+machine['name']+"\"", - ancestorNoteId=trilium_htb_folder, - ancestorDepth='eq1', - limit=1, - fastSearch=True, - ) - - if res['results'] and machine['name'] == res['results'][0]['title']: - # page exists - lets check if the details have changed - - current_html = ea.get_note_content(noteId=res['results'][0]['noteId']) - current_soup = BeautifulSoup(current_html, 'html.parser') - current_paragraph = current_soup.find_all('p')[0].text - - new_html = generate_newpage(machine) - new_soup = BeautifulSoup(new_html, 'html.parser') - new_paragraph = new_soup.find_all('p')[0].text - - # current page contains first paragraph of "blank" (useful for when it doesnt create or find the note properly.. shouldnt get here) - if current_paragraph == "blank": - ea.update_note_content(noteId=res['results'][0]['noteId'], content=new_html) - ea.create_attribute(attributeId=None, isInheritable=False, noteId=res['results'][0]['noteId'], type="relation", name="template", value=trilium_template_id) - if machine['authUserInUserOwns'] and machine['authUserInRootOwns']: - ea.create_attribute(attributeId=None, isInheritable=False, noteId=res['results'][0]['noteId'], type="label", name="cssClass", value="done") - else: - if machine['authUserInUserOwns'] or machine['authUserInRootOwns']: - ea.create_attribute(attributeId=None, isInheritable=False, noteId=res['results'][0]['noteId'], type="label", name="cssClass", value="inprogress") - else: - ea.create_attribute(attributeId=None, isInheritable=False, noteId=res['results'][0]['noteId'], type="label", name="cssClass", value="todo") - # re-get the current content - current_html = ea.get_note_content(noteId=res['results'][0]['noteId']) - current_soup = BeautifulSoup(current_html, 'html.parser') - - if current_paragraph != new_paragraph: - - # details have updated! - print("[+] updating page:",machine['name'], "-> "+res['results'][0]['title']+" ") - replacement = current_soup.find('p') - replacement.replace_with( new_soup.find_all('p')[0] ) - ea.update_note_content(noteId=res['results'][0]['noteId'], content=current_soup) - - # now to update the label - for attribute in res['results'][0]['attributes']: - if attribute['name'] == "cssClass": - if machine['authUserInUserOwns'] and machine['authUserInRootOwns']: - ea.patch_attribute(attributeId=attribute['attributeId'], value="done") - else: - if machine['authUserInUserOwns'] or machine['authUserInRootOwns']: - ea.patch_attribute(attributeId=attribute['attributeId'], value="inprogress") - else: - ea.patch_attribute(attributeId=attribute['attributeId'], value="todo") - - else: - # title does not exist - create the note - html = generate_newpage(machine) - new_note = ea.create_note( - parentNoteId=trilium_htb_folder, - type="text", - title=machine['name'], - content=html, - ) - print("[+] created note:", machine['name'], " ") - - ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="relation", name="template", value=trilium_template_id) - if machine['authUserInUserOwns'] and machine['authUserInRootOwns']: - ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="done") - else: - if machine['authUserInUserOwns'] or machine['authUserInRootOwns']: - ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="inprogress") - else: - ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="todo") - - if machine['authUserInUserOwns'] and machine['authUserInRootOwns']: - completed_count += 1 - -print("[+] updating folder name ") -ea.patch_note(noteId=trilium_htb_folder,title="Machines - "+str(completed_count)+" / "+str(len(machines))) - -print("[=] processed", machine_count) \ No newline at end of file diff --git a/htb2trilium2.py b/htb2trilium2.py deleted file mode 100644 index c831574..0000000 --- a/htb2trilium2.py +++ /dev/null @@ -1,224 +0,0 @@ -''' -######################## -# SETUP -######################## - > pip3 install trilium-py - - - get trilium token in trilium options->ETAPI and "create new token" - - get trilium_htb_folder click on note to become folder -> "note info". Note ID is there. - - set the folder's owned attributes to: - #label:user=promoted,single,text #label:root=promoted,single,text #label:respect=promoted,single,text #user=0 #root=0 #respect=0 - - - create a note named "HTBMachineTemplate" and set it's "owned attributes" to: - #template #label:User=promoted,single,text #label:Root=promoted,single,text #label:Tags=promoted,single,text - - - get this page's ID and put in "trilium_template_id" - - to get "in progress" todo colour - add to trilium demo/scripting/taskmanager/implementation/CSS: - - span.fancytree-node.inprogress .fancytree-title { - color: orange !important; - } -''' - -######################## -# IGNORE THESE -######################## - -import requests -from bs4 import BeautifulSoup -from datetime import datetime, timezone -from htb_client import HTBClient -from trilium_py.client import ETAPI - -######################## -# EDIT BELOW HERE -######################## - -htb_code = '' -trilium_server_url = 'https://notes.place.com' -trilium_token = '' -trilium_htb_folder = '' -trilium_template_id = '' - -######################## -# LEAVE BELOW HERE -######################## - -def generate_newpage(machine): - no = "No" - yes = "Yes" - user_colour = no - if machine['authUserInUserOwns']: - user_colour = yes - root_colour = no - if machine['authUserInRootOwns']: - root_colour = yes - - status = "Retired" - if machine['active']: - status = "Active" - - release_str = machine['release'] - release_date = datetime.strptime(release_str, "%Y-%m-%dT%H:%M:%S.%fZ") - formatted_release_date = release_date.strftime("%d %B %Y") - - html = """ -
- -
-

- OS: {os}
- Difficulty: {difficultyText}
- Rating: {rating} / 5
- Points: {points}
- User / Root: {user_colour} / {root_colour}
- Released: {release_date}
- State: {status} -

-
-

Notes

-

 

- """.format( - os=machine['os'], - difficultyText=machine['difficultyText'], - rating=machine['star'], - points=machine['points'], - release_date=formatted_release_date, - user_colour=user_colour, - root_colour=root_colour, - status = status, - avatar = machine['avatar'], - ) - - return html - -def get_timestamp(machine): - # Parse the release date string into a datetime object - release_str = machine['release'] - dt = datetime.strptime(release_str, "%Y-%m-%dT%H:%M:%S.%fZ") - - # Set the timezone to UTC - dt = dt.replace(tzinfo=timezone.utc) - - # Get the timestamp - timestamp = dt.timestamp() - return timestamp - -print("[+] connecting to HTB") -client = HTBClient(password=htb_code) -print("[+] connecting to trilium") -ea = ETAPI(trilium_server_url, trilium_token) -print("[i] version: ", ea.app_info()['appVersion']) - -print("[i] HTB User:", client.user['id'], "-", client.user['name']) -print("[i] user owns:", client.user['user_owns'], "| Root owns:", client.user['root_owns'], "| Respect:", client.user['respects']) - -master_folder = ea.get_note(trilium_htb_folder) -for attribute in master_folder['attributes']: - if attribute['name'] == "user": - if attribute['value'] != str(client.user['user_owns']): - print("[+] updating user owns (folder attribute)") - ea.patch_attribute(attributeId=attribute['attributeId'], value=str(client.user['user_owns'])) - if attribute['name'] == "root": - if attribute['value'] != str(client.user['root_owns']): - print("[+] updating root owns (folder attribute)") - ea.patch_attribute(attributeId=attribute['attributeId'], value=str(client.user['root_owns'])) - if attribute['name'] == "respect": - if attribute['value'] != str(client.user['respects']): - print("[+] updating respect (folder attribute)") - ea.patch_attribute(attributeId=attribute['attributeId'], value=str(client.user['respects'])) - -print("[+] gathering machines info") -machines = client.get_all_machines() -machines.sort(key=get_timestamp) - -print(f"[i] Retrieved {len(machines)} machines") -#for machine in machines: -# print(f" - ID: {machine['id']}, Name: {machine['name']}, OS: {machine['os']}, Difficulty: {machine['difficultyText']}") - -machine_count = 0 -completed_count = 0 -for machine in machines: - machine_count += 1 - print('processing: ',machine_count, "/", len(machines), "("+machine['name']+") " , end='\r') - - res = ea.search_note( - search="\""+machine['name']+"\"", - ancestorNoteId=trilium_htb_folder, - ancestorDepth='eq1', - limit=1, - fastSearch=True, - ) - - if res['results'] and machine['name'] == res['results'][0]['title']: - # page exists - lets check if the details have changed - - current_html = ea.get_note_content(noteId=res['results'][0]['noteId']) - current_soup = BeautifulSoup(current_html, 'html.parser') - current_paragraph = current_soup.find_all('p')[0].text - - new_html = generate_newpage(machine) - new_soup = BeautifulSoup(new_html, 'html.parser') - new_paragraph = new_soup.find_all('p')[0].text - - # current page contains first paragraph of "blank" (useful for when it doesnt create or find the note properly.. shouldnt get here) - if current_paragraph == "blank": - ea.update_note_content(noteId=res['results'][0]['noteId'], content=new_html) - ea.create_attribute(attributeId=None, isInheritable=False, noteId=res['results'][0]['noteId'], type="relation", name="template", value=trilium_template_id) - if machine['authUserInUserOwns'] and machine['authUserInRootOwns']: - ea.create_attribute(attributeId=None, isInheritable=False, noteId=res['results'][0]['noteId'], type="label", name="cssClass", value="done") - else: - if machine['authUserInUserOwns'] or machine['authUserInRootOwns']: - ea.create_attribute(attributeId=None, isInheritable=False, noteId=res['results'][0]['noteId'], type="label", name="cssClass", value="inprogress") - else: - ea.create_attribute(attributeId=None, isInheritable=False, noteId=res['results'][0]['noteId'], type="label", name="cssClass", value="todo") - # re-get the current content - current_html = ea.get_note_content(noteId=res['results'][0]['noteId']) - current_soup = BeautifulSoup(current_html, 'html.parser') - - if current_paragraph != new_paragraph: - - # details have updated! - print("[+] updating page:",machine['name'], "-> "+res['results'][0]['title']+" ") - replacement = current_soup.find('p') - replacement.replace_with( new_soup.find_all('p')[0] ) - ea.update_note_content(noteId=res['results'][0]['noteId'], content=current_soup) - - # now to update the label - for attribute in res['results'][0]['attributes']: - if attribute['name'] == "cssClass": - if machine['authUserInUserOwns'] and machine['authUserInRootOwns']: - ea.patch_attribute(attributeId=attribute['attributeId'], value="done") - else: - if machine['authUserInUserOwns'] or machine['authUserInRootOwns']: - ea.patch_attribute(attributeId=attribute['attributeId'], value="inprogress") - else: - ea.patch_attribute(attributeId=attribute['attributeId'], value="todo") - - else: - # title does not exist - create the note - html = generate_newpage(machine) - new_note = ea.create_note( - parentNoteId=trilium_htb_folder, - type="text", - title=machine['name'], - content=html, - ) - print("[+] created note:", machine['name'], " ") - - ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="relation", name="template", value=trilium_template_id) - if machine['authUserInUserOwns'] and machine['authUserInRootOwns']: - ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="done") - else: - if machine['authUserInUserOwns'] or machine['authUserInRootOwns']: - ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="inprogress") - else: - ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="todo") - - if machine['authUserInUserOwns'] and machine['authUserInRootOwns']: - completed_count += 1 - -print("[+] updating folder name ") -ea.patch_note(noteId=trilium_htb_folder,title="Machines - "+str(completed_count)+" / "+str(len(machines))) - -print("[=] processed", machine_count) \ No newline at end of file diff --git a/htb_client.py b/htb_client.py deleted file mode 100644 index 6d3631e..0000000 --- a/htb_client.py +++ /dev/null @@ -1,146 +0,0 @@ -import requests -import warnings - -warnings.filterwarnings("ignore") - -class HTBClient: - def __init__(self, password): - self.password = password - self.base_url = 'https://labs.hackthebox.com/api/v4' - self.proxies = { - #"http": "http://127.0.0.1:8080", # Burp proxy for HTTP traffic - #"https": "http://127.0.0.1:8080" # Burp proxy for HTTPS traffic - } - # Fetch user info - self.user = self.get_user_info() - - # Fetch user stats and store them in self.user - user_owns, root_owns, respects = self.get_user_stats(self.user['id']) - self.user['user_owns'] = user_owns - self.user['root_owns'] = root_owns - self.user['respects'] = respects - - def get_user_info(self): - headers = { - "Authorization": f"Bearer {self.password}", - "User-Agent": None # Explicitly remove User-Agent - } - response = requests.get( - f'{self.base_url}/user/info', - headers=headers, - proxies=self.proxies, - verify=False # Disable SSL verification - ) - if response.status_code != 200: - raise Exception(f"Error fetching {self.base_url}/user/info user info: {response.status_code}, {response.text}") - - # Return the user info as a dictionary - data = response.json().get('info') - return {'id': data['id'], 'name': data['name'], 'email': data['email']} - - def get_user_stats(self, user_id): - headers = { - "Authorization": f"Bearer {self.password}", - "User-Agent": None # Explicitly remove User-Agent - } - response = requests.get( - f'{self.base_url}/user/profile/basic/{user_id}', - headers=headers, - proxies=self.proxies, - verify=False # Disable SSL verification - ) - if response.status_code != 200: - raise Exception(f"Error fetching user stats: {response.status_code}, {response.text}") - - # Extract user statistics from the response - data = response.json().get('profile') - user_owns = data['user_owns'] - root_owns = data['system_owns'] - respects = data['respects'] - return user_owns, root_owns, respects - - def get_active_machines(self): - machines = [] - seen_ids = set() # Track unique machine IDs - seen_names = set() # Track unique machine names - page = 1 - - while True: - response = requests.get( - f'{self.base_url}/machine/paginated?per_page=100&page={page}', - headers={ - "Authorization": f"Bearer {self.password}", - "User-Agent": None # Explicitly remove User-Agent - }, - proxies=self.proxies, - verify=False # Disable SSL verification - ) - - if response.status_code != 200: - raise Exception(f"Error fetching active machines: {response.status_code}, {response.text}") - - data = response.json() - for machine in data['data']: - if machine['id'] not in seen_ids and machine['name'] not in seen_names: - machines.append(machine) - seen_ids.add(machine['id']) - seen_names.add(machine['name']) - - # Check for pagination - if page >= data['meta']['last_page']: - break - page += 1 - - return machines - - def get_retired_machines(self): - machines = [] - seen_ids = set() # Track unique machine IDs - seen_names = set() # Track unique machine names - page = 1 - - while True: - response = requests.get( - f'{self.base_url}/machine/list/retired/paginated?per_page=100&page={page}', - headers={ - "Authorization": f"Bearer {self.password}", - "User-Agent": None # Explicitly remove User-Agent - }, - proxies=self.proxies, - verify=False # Disable SSL verification - ) - - if response.status_code != 200: - raise Exception(f"Error fetching retired machines: {response.status_code}, {response.text}") - - data = response.json() - for machine in data['data']: - if machine['id'] not in seen_ids and machine['name'] not in seen_names: - machines.append(machine) - seen_ids.add(machine['id']) - seen_names.add(machine['name']) - - # Check for pagination - if page >= data['meta']['last_page']: - break - page += 1 - - return machines - - def get_all_machines(self): - # Combine active and retired machines, ensuring no duplicates - active_machines = self.get_active_machines() - retired_machines = self.get_retired_machines() - - all_machines = active_machines + retired_machines - seen_ids = set() # Track unique machine IDs - seen_names = set() # Track unique machine names - unique_machines = [] - - for machine in all_machines: - if machine['id'] not in seen_ids and machine['name'] not in seen_names: - unique_machines.append(machine) - seen_ids.add(machine['id']) - seen_names.add(machine['name']) - - return unique_machines \ No newline at end of file