import os import json import requests from bs4 import BeautifulSoup from datetime import datetime, timezone from htb_client import HTBClient from trilium_py.client import ETAPI # Get the absolute path of the script's directory script_dir = os.path.dirname(os.path.abspath(__file__)) # Construct the full path to the config.json file config_path = os.path.join(script_dir, 'config.json') # Load configuration from the JSON file with open(config_path, 'r') as f: config = json.load(f) # Accessing config values htb_code = config['htb_code'] trilium_server_url = config['trilium_server_url'] trilium_token = config['trilium_token'] trilium_machines_htb_folder = config['trilium_machines_htb_folder'] trilium_machines_template_id = config['trilium_machines_template_id'] def generate_newpage(machine): no = "<span style=\"color:hsl(0,75%,60%);\">No</span>" yes = "<span style=\"color:hsl(120,75%,60%);\">Yes</span>" 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 = """ <figure class="image image-style-align-left image_resized" style="width:12.28%;"> <img src="https://www.hackthebox.com{avatar}"> </figure> <p> <strong>OS:</strong> {os}<br> <strong>Difficulty:</strong> {difficultyText} <br> <strong>Rating:</strong> {rating} / 5<br> <strong>Points:</strong> {points}<br> <strong>User / Root: </strong> {user_colour} / {root_colour}<br> <strong>Released:</strong> {release_date}<br> <strong>State:</strong> {status} </p> <hr> <h2>Notes</h2> <p> </p> """.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_machines_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_machines_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_machines_htb_folder) 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_machines_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_machines_htb_folder) 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_machines_htb_folder,title="Machines - "+str(completed_count)+" / "+str(len(machines))) print("[=] processed", machine_count)