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://htb-mp-prod-public-storage.s3.eu-central-1.amazonaws.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')
escaped_title = machine['name'].replace("'", "\\'")
res = ea.search_note(
search=f"note.title = '{escaped_title}'",
ancestorNoteId=trilium_machines_htb_folder,
ancestorDepth='eq1',
limit=1,
)
if res['results'] and machine['name'].strip().lower() == res['results'][0]['title'].strip().lower():
# 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)