diff --git a/INSTALL.md b/INSTALL.md
new file mode 100644
index 0000000..9fe725b
--- /dev/null
+++ b/INSTALL.md
@@ -0,0 +1,32 @@
+## scripts require:
+
+ > pip3 install trilium-py
+
+## config.json
+ - get trilium token in trilium options->ETAPI and "create new token"
+ - create a new note with a title "Machines"
+ - click on this note -> "note info". Note ID is there. This ID goes in "trilium_machines_htb_folder" in the config file
+ - 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" 
+ - 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_machines_template_id"
+
+- create "challenges" page, this page's ID goes in: trilium_challenges_folder  
+- create "Sherlocks" page, this page's ID goes in: trilium_sherlocks_folder  
+- create 2 pages "HTBChallengesTemplate" and "HTBSherlocksTemplate", both of these should have the owned attributes:
+```#template #label:User=promoted,single,text #label:Root=promoted,single,text #label:Tags=promoted,single,text ```
+- The ID's of "HTBChallengesTemplate" and "HTBSherlocksTemplate" go in the matching config values.
+
+ - to get "in progress" todo colour - add to trilium demo/scripting/taskmanager/implementation/CSS:
+
+```
+ span.fancytree-node.inprogress .fancytree-title {
+    color: orange !important;
+ }
+```
\ No newline at end of file

diff --git a/INSTALL.md b/INSTALL.md
new file mode 100644
index 0000000..9fe725b
--- /dev/null
+++ b/INSTALL.md
@@ -0,0 +1,32 @@
+## scripts require:
+
+ > pip3 install trilium-py
+
+## config.json
+ - get trilium token in trilium options->ETAPI and "create new token"
+ - create a new note with a title "Machines"
+ - click on this note -> "note info". Note ID is there. This ID goes in "trilium_machines_htb_folder" in the config file
+ - 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" 
+ - 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_machines_template_id"
+
+- create "challenges" page, this page's ID goes in: trilium_challenges_folder  
+- create "Sherlocks" page, this page's ID goes in: trilium_sherlocks_folder  
+- create 2 pages "HTBChallengesTemplate" and "HTBSherlocksTemplate", both of these should have the owned attributes:
+```#template #label:User=promoted,single,text #label:Root=promoted,single,text #label:Tags=promoted,single,text ```
+- The ID's of "HTBChallengesTemplate" and "HTBSherlocksTemplate" go in the matching config values.
+
+ - to get "in progress" todo colour - add to trilium demo/scripting/taskmanager/implementation/CSS:
+
+```
+ span.fancytree-node.inprogress .fancytree-title {
+    color: orange !important;
+ }
+```
\ No newline at end of file
diff --git a/config.json b/config.json
new file mode 100644
index 0000000..39597ce
--- /dev/null
+++ b/config.json
@@ -0,0 +1,11 @@
+{
+	"htb_code": "in https://app.hackthebox.com/profile/settings click 'profile settings' and under 'app tokens' you can 'create app token' - that goes here",
+	"trilium_server_url": "https://place.to.your.trilium.server",
+	"trilium_token": "",
+	"trilium_machines_htb_folder": "",
+	"trilium_machines_template_id": "",
+	"trilium_challenges_folder": "",
+	"trilium_challenges_template_id": "",
+	"trilium_sherlocks_folder": "",
+	"trilium_sherlocks_template_id": ""
+}
\ No newline at end of file

diff --git a/INSTALL.md b/INSTALL.md
new file mode 100644
index 0000000..9fe725b
--- /dev/null
+++ b/INSTALL.md
@@ -0,0 +1,32 @@
+## scripts require:
+
+ > pip3 install trilium-py
+
+## config.json
+ - get trilium token in trilium options->ETAPI and "create new token"
+ - create a new note with a title "Machines"
+ - click on this note -> "note info". Note ID is there. This ID goes in "trilium_machines_htb_folder" in the config file
+ - 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" 
+ - 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_machines_template_id"
+
+- create "challenges" page, this page's ID goes in: trilium_challenges_folder  
+- create "Sherlocks" page, this page's ID goes in: trilium_sherlocks_folder  
+- create 2 pages "HTBChallengesTemplate" and "HTBSherlocksTemplate", both of these should have the owned attributes:
+```#template #label:User=promoted,single,text #label:Root=promoted,single,text #label:Tags=promoted,single,text ```
+- The ID's of "HTBChallengesTemplate" and "HTBSherlocksTemplate" go in the matching config values.
+
+ - to get "in progress" todo colour - add to trilium demo/scripting/taskmanager/implementation/CSS:
+
+```
+ span.fancytree-node.inprogress .fancytree-title {
+    color: orange !important;
+ }
+```
\ No newline at end of file
diff --git a/config.json b/config.json
new file mode 100644
index 0000000..39597ce
--- /dev/null
+++ b/config.json
@@ -0,0 +1,11 @@
+{
+	"htb_code": "in https://app.hackthebox.com/profile/settings click 'profile settings' and under 'app tokens' you can 'create app token' - that goes here",
+	"trilium_server_url": "https://place.to.your.trilium.server",
+	"trilium_token": "",
+	"trilium_machines_htb_folder": "",
+	"trilium_machines_template_id": "",
+	"trilium_challenges_folder": "",
+	"trilium_challenges_template_id": "",
+	"trilium_sherlocks_folder": "",
+	"trilium_sherlocks_template_id": ""
+}
\ No newline at end of file
diff --git a/htb2trilium_challenges.py b/htb2trilium_challenges.py
new file mode 100644
index 0000000..0c08b74
--- /dev/null
+++ b/htb2trilium_challenges.py
@@ -0,0 +1,146 @@
+import os
+import json
+import requests
+from collections import defaultdict
+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_challenges_folder = config['trilium_challenges_folder']
+trilium_challenges_template_id = config['trilium_challenges_template_id']
+
+def get_timestamp(machine):
+    # Parse the release date string into a datetime object
+    release_str = machine['release_date']
+    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("[+] gathering challenges info")
+categories = defaultdict(list)
+challenges = client.get_all_challenges()
+challenges.sort(key=get_timestamp)
+total_completed = 0  # Variable to track total completed challenges
+
+print(f"[i] Retrieved {len(challenges)} challenges")
+
+# Group challenges by their categories
+for challenge in challenges:
+    categories[challenge['category_name']].append(challenge)
+    if challenge['is_owned']:
+        total_completed += 1  # Increment total completed if challenge is owned
+
+# Print out the grouped challenges with the number of completed and total challenges in each category
+for category, grouped_challenges in categories.items():
+    total = len(grouped_challenges)
+    completed = sum(1 for challenge in grouped_challenges if challenge['is_owned'])  # Count completed challenges
+    
+    res = ea.search_note(
+        search=f"note.title %= '{category}*'",
+        ancestorNoteId=trilium_challenges_folder,
+        ancestorDepth='eq1',
+        limit=1,
+        fastSearch=True,
+    )
+    
+    catId = ""
+    if res['results'] and res['results'][0]['title'].lower() == category.lower()+" - "+str(completed)+" / "+str(total):
+		# page exists - lets check if the details have changed
+        ea.patch_note(noteId=res['results'][0]['noteId'], title=category+" - "+str(completed)+" / "+str(total))
+        catId = res['results'][0]['noteId']
+        print(f"[i] updated category: {category} - ({completed}/{total})")
+    else:
+        new_note = ea.create_note(
+            parentNoteId=trilium_challenges_folder,
+            type="text",
+            title=category+" - "+str(completed)+" / "+str(total),
+            content=" ",
+        )
+        catId = new_note['note']['noteId']
+        print(f"[+] created category: {catId} {category} - ({completed}/{total})")
+    
+    for challenge in grouped_challenges:
+        #print(f" - ID: {challenge['id']}, Name: {challenge['name']}, Difficulty: {challenge['difficulty']}")
+        res2 = ea.search_note(
+            search=f"{challenge['name']}",
+            ancestorNoteId=catId,
+            ancestorDepth='eq1',
+            limit=1,
+            fastSearch=True,
+        )
+        # already exists update the values
+        if res2['results'] and res2['results'][0]['title'].lower() == challenge['name'].lower():
+            print(f"[i] found ID: {challenge['id']}, Name: {challenge['name']}, Difficulty: {challenge['difficulty']}")
+            #print(f"Search response for challenge '{challenge['name']}': {res2}")
+            for attribute in res2['results'][0]['attributes']:
+                if attribute['name'] == "Difficulty":
+                    ea.patch_attribute(attributeId=attribute['attributeId'], value=challenge['difficulty'])
+                if attribute['name'] == "Released":
+                    release_str = challenge['release_date']
+                    release_date = datetime.strptime(release_str, "%Y-%m-%dT%H:%M:%S.%fZ")
+                    formatted_release_date = release_date.strftime("%d %B %Y")
+                    ea.patch_attribute(attributeId=attribute['attributeId'], value=formatted_release_date)
+                if attribute['name'] == "Solved":
+                    if challenge['is_owned']:
+                        ea.patch_attribute(attributeId=attribute['attributeId'], value="done")
+                    else:
+                        ea.patch_attribute(attributeId=attribute['attributeId'], value=" ")
+                if attribute['name'] == "cssClass":
+                    if challenge['is_owned']:
+                        ea.patch_attribute(attributeId=attribute['attributeId'], value="done")
+                    else:
+                        ea.patch_attribute(attributeId=attribute['attributeId'], value="todo")
+
+        else: # doesnt already exist, create page
+            release_str = challenge['release_date']
+            release_date = datetime.strptime(release_str, "%Y-%m-%dT%H:%M:%S.%fZ")
+            formatted_release_date = release_date.strftime("%d %B %Y")
+            new_note = ea.create_note(
+                parentNoteId=catId,
+                type="text",
+                title=challenge['name'],
+                content=" ",
+            )
+            ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="relation", name="template", value=trilium_challenges_template_id)
+            ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="Difficulty", value=challenge['difficulty'])
+            ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="Released", value=formatted_release_date)
+            if challenge['is_owned']:
+                ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="done")
+                ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="Solved", value="done")
+            else:
+                ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="todo")
+                ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="Solved", value=" ")
+
+            print(f"[+] created ID: {challenge['id']}, Name: {challenge['name']}, Difficulty: {challenge['difficulty']}")
+
+print("[+] updating folder name                                          ")
+ea.patch_note(noteId=trilium_challenges_folder,title="Challenges - "+str(total_completed)+" / "+str(len(challenges)))
\ No newline at end of file

diff --git a/INSTALL.md b/INSTALL.md
new file mode 100644
index 0000000..9fe725b
--- /dev/null
+++ b/INSTALL.md
@@ -0,0 +1,32 @@
+## scripts require:
+
+ > pip3 install trilium-py
+
+## config.json
+ - get trilium token in trilium options->ETAPI and "create new token"
+ - create a new note with a title "Machines"
+ - click on this note -> "note info". Note ID is there. This ID goes in "trilium_machines_htb_folder" in the config file
+ - 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" 
+ - 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_machines_template_id"
+
+- create "challenges" page, this page's ID goes in: trilium_challenges_folder  
+- create "Sherlocks" page, this page's ID goes in: trilium_sherlocks_folder  
+- create 2 pages "HTBChallengesTemplate" and "HTBSherlocksTemplate", both of these should have the owned attributes:
+```#template #label:User=promoted,single,text #label:Root=promoted,single,text #label:Tags=promoted,single,text ```
+- The ID's of "HTBChallengesTemplate" and "HTBSherlocksTemplate" go in the matching config values.
+
+ - to get "in progress" todo colour - add to trilium demo/scripting/taskmanager/implementation/CSS:
+
+```
+ span.fancytree-node.inprogress .fancytree-title {
+    color: orange !important;
+ }
+```
\ No newline at end of file
diff --git a/config.json b/config.json
new file mode 100644
index 0000000..39597ce
--- /dev/null
+++ b/config.json
@@ -0,0 +1,11 @@
+{
+	"htb_code": "in https://app.hackthebox.com/profile/settings click 'profile settings' and under 'app tokens' you can 'create app token' - that goes here",
+	"trilium_server_url": "https://place.to.your.trilium.server",
+	"trilium_token": "",
+	"trilium_machines_htb_folder": "",
+	"trilium_machines_template_id": "",
+	"trilium_challenges_folder": "",
+	"trilium_challenges_template_id": "",
+	"trilium_sherlocks_folder": "",
+	"trilium_sherlocks_template_id": ""
+}
\ No newline at end of file
diff --git a/htb2trilium_challenges.py b/htb2trilium_challenges.py
new file mode 100644
index 0000000..0c08b74
--- /dev/null
+++ b/htb2trilium_challenges.py
@@ -0,0 +1,146 @@
+import os
+import json
+import requests
+from collections import defaultdict
+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_challenges_folder = config['trilium_challenges_folder']
+trilium_challenges_template_id = config['trilium_challenges_template_id']
+
+def get_timestamp(machine):
+    # Parse the release date string into a datetime object
+    release_str = machine['release_date']
+    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("[+] gathering challenges info")
+categories = defaultdict(list)
+challenges = client.get_all_challenges()
+challenges.sort(key=get_timestamp)
+total_completed = 0  # Variable to track total completed challenges
+
+print(f"[i] Retrieved {len(challenges)} challenges")
+
+# Group challenges by their categories
+for challenge in challenges:
+    categories[challenge['category_name']].append(challenge)
+    if challenge['is_owned']:
+        total_completed += 1  # Increment total completed if challenge is owned
+
+# Print out the grouped challenges with the number of completed and total challenges in each category
+for category, grouped_challenges in categories.items():
+    total = len(grouped_challenges)
+    completed = sum(1 for challenge in grouped_challenges if challenge['is_owned'])  # Count completed challenges
+    
+    res = ea.search_note(
+        search=f"note.title %= '{category}*'",
+        ancestorNoteId=trilium_challenges_folder,
+        ancestorDepth='eq1',
+        limit=1,
+        fastSearch=True,
+    )
+    
+    catId = ""
+    if res['results'] and res['results'][0]['title'].lower() == category.lower()+" - "+str(completed)+" / "+str(total):
+		# page exists - lets check if the details have changed
+        ea.patch_note(noteId=res['results'][0]['noteId'], title=category+" - "+str(completed)+" / "+str(total))
+        catId = res['results'][0]['noteId']
+        print(f"[i] updated category: {category} - ({completed}/{total})")
+    else:
+        new_note = ea.create_note(
+            parentNoteId=trilium_challenges_folder,
+            type="text",
+            title=category+" - "+str(completed)+" / "+str(total),
+            content=" ",
+        )
+        catId = new_note['note']['noteId']
+        print(f"[+] created category: {catId} {category} - ({completed}/{total})")
+    
+    for challenge in grouped_challenges:
+        #print(f" - ID: {challenge['id']}, Name: {challenge['name']}, Difficulty: {challenge['difficulty']}")
+        res2 = ea.search_note(
+            search=f"{challenge['name']}",
+            ancestorNoteId=catId,
+            ancestorDepth='eq1',
+            limit=1,
+            fastSearch=True,
+        )
+        # already exists update the values
+        if res2['results'] and res2['results'][0]['title'].lower() == challenge['name'].lower():
+            print(f"[i] found ID: {challenge['id']}, Name: {challenge['name']}, Difficulty: {challenge['difficulty']}")
+            #print(f"Search response for challenge '{challenge['name']}': {res2}")
+            for attribute in res2['results'][0]['attributes']:
+                if attribute['name'] == "Difficulty":
+                    ea.patch_attribute(attributeId=attribute['attributeId'], value=challenge['difficulty'])
+                if attribute['name'] == "Released":
+                    release_str = challenge['release_date']
+                    release_date = datetime.strptime(release_str, "%Y-%m-%dT%H:%M:%S.%fZ")
+                    formatted_release_date = release_date.strftime("%d %B %Y")
+                    ea.patch_attribute(attributeId=attribute['attributeId'], value=formatted_release_date)
+                if attribute['name'] == "Solved":
+                    if challenge['is_owned']:
+                        ea.patch_attribute(attributeId=attribute['attributeId'], value="done")
+                    else:
+                        ea.patch_attribute(attributeId=attribute['attributeId'], value=" ")
+                if attribute['name'] == "cssClass":
+                    if challenge['is_owned']:
+                        ea.patch_attribute(attributeId=attribute['attributeId'], value="done")
+                    else:
+                        ea.patch_attribute(attributeId=attribute['attributeId'], value="todo")
+
+        else: # doesnt already exist, create page
+            release_str = challenge['release_date']
+            release_date = datetime.strptime(release_str, "%Y-%m-%dT%H:%M:%S.%fZ")
+            formatted_release_date = release_date.strftime("%d %B %Y")
+            new_note = ea.create_note(
+                parentNoteId=catId,
+                type="text",
+                title=challenge['name'],
+                content=" ",
+            )
+            ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="relation", name="template", value=trilium_challenges_template_id)
+            ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="Difficulty", value=challenge['difficulty'])
+            ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="Released", value=formatted_release_date)
+            if challenge['is_owned']:
+                ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="done")
+                ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="Solved", value="done")
+            else:
+                ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="todo")
+                ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="Solved", value=" ")
+
+            print(f"[+] created ID: {challenge['id']}, Name: {challenge['name']}, Difficulty: {challenge['difficulty']}")
+
+print("[+] updating folder name                                          ")
+ea.patch_note(noteId=trilium_challenges_folder,title="Challenges - "+str(total_completed)+" / "+str(len(challenges)))
\ No newline at end of file
diff --git a/htb2trilium_machines.py b/htb2trilium_machines.py
new file mode 100644
index 0000000..de8be53
--- /dev/null
+++ b/htb2trilium_machines.py
@@ -0,0 +1,203 @@
+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>&nbsp;</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)
\ No newline at end of file

diff --git a/INSTALL.md b/INSTALL.md
new file mode 100644
index 0000000..9fe725b
--- /dev/null
+++ b/INSTALL.md
@@ -0,0 +1,32 @@
+## scripts require:
+
+ > pip3 install trilium-py
+
+## config.json
+ - get trilium token in trilium options->ETAPI and "create new token"
+ - create a new note with a title "Machines"
+ - click on this note -> "note info". Note ID is there. This ID goes in "trilium_machines_htb_folder" in the config file
+ - 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" 
+ - 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_machines_template_id"
+
+- create "challenges" page, this page's ID goes in: trilium_challenges_folder  
+- create "Sherlocks" page, this page's ID goes in: trilium_sherlocks_folder  
+- create 2 pages "HTBChallengesTemplate" and "HTBSherlocksTemplate", both of these should have the owned attributes:
+```#template #label:User=promoted,single,text #label:Root=promoted,single,text #label:Tags=promoted,single,text ```
+- The ID's of "HTBChallengesTemplate" and "HTBSherlocksTemplate" go in the matching config values.
+
+ - to get "in progress" todo colour - add to trilium demo/scripting/taskmanager/implementation/CSS:
+
+```
+ span.fancytree-node.inprogress .fancytree-title {
+    color: orange !important;
+ }
+```
\ No newline at end of file
diff --git a/config.json b/config.json
new file mode 100644
index 0000000..39597ce
--- /dev/null
+++ b/config.json
@@ -0,0 +1,11 @@
+{
+	"htb_code": "in https://app.hackthebox.com/profile/settings click 'profile settings' and under 'app tokens' you can 'create app token' - that goes here",
+	"trilium_server_url": "https://place.to.your.trilium.server",
+	"trilium_token": "",
+	"trilium_machines_htb_folder": "",
+	"trilium_machines_template_id": "",
+	"trilium_challenges_folder": "",
+	"trilium_challenges_template_id": "",
+	"trilium_sherlocks_folder": "",
+	"trilium_sherlocks_template_id": ""
+}
\ No newline at end of file
diff --git a/htb2trilium_challenges.py b/htb2trilium_challenges.py
new file mode 100644
index 0000000..0c08b74
--- /dev/null
+++ b/htb2trilium_challenges.py
@@ -0,0 +1,146 @@
+import os
+import json
+import requests
+from collections import defaultdict
+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_challenges_folder = config['trilium_challenges_folder']
+trilium_challenges_template_id = config['trilium_challenges_template_id']
+
+def get_timestamp(machine):
+    # Parse the release date string into a datetime object
+    release_str = machine['release_date']
+    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("[+] gathering challenges info")
+categories = defaultdict(list)
+challenges = client.get_all_challenges()
+challenges.sort(key=get_timestamp)
+total_completed = 0  # Variable to track total completed challenges
+
+print(f"[i] Retrieved {len(challenges)} challenges")
+
+# Group challenges by their categories
+for challenge in challenges:
+    categories[challenge['category_name']].append(challenge)
+    if challenge['is_owned']:
+        total_completed += 1  # Increment total completed if challenge is owned
+
+# Print out the grouped challenges with the number of completed and total challenges in each category
+for category, grouped_challenges in categories.items():
+    total = len(grouped_challenges)
+    completed = sum(1 for challenge in grouped_challenges if challenge['is_owned'])  # Count completed challenges
+    
+    res = ea.search_note(
+        search=f"note.title %= '{category}*'",
+        ancestorNoteId=trilium_challenges_folder,
+        ancestorDepth='eq1',
+        limit=1,
+        fastSearch=True,
+    )
+    
+    catId = ""
+    if res['results'] and res['results'][0]['title'].lower() == category.lower()+" - "+str(completed)+" / "+str(total):
+		# page exists - lets check if the details have changed
+        ea.patch_note(noteId=res['results'][0]['noteId'], title=category+" - "+str(completed)+" / "+str(total))
+        catId = res['results'][0]['noteId']
+        print(f"[i] updated category: {category} - ({completed}/{total})")
+    else:
+        new_note = ea.create_note(
+            parentNoteId=trilium_challenges_folder,
+            type="text",
+            title=category+" - "+str(completed)+" / "+str(total),
+            content=" ",
+        )
+        catId = new_note['note']['noteId']
+        print(f"[+] created category: {catId} {category} - ({completed}/{total})")
+    
+    for challenge in grouped_challenges:
+        #print(f" - ID: {challenge['id']}, Name: {challenge['name']}, Difficulty: {challenge['difficulty']}")
+        res2 = ea.search_note(
+            search=f"{challenge['name']}",
+            ancestorNoteId=catId,
+            ancestorDepth='eq1',
+            limit=1,
+            fastSearch=True,
+        )
+        # already exists update the values
+        if res2['results'] and res2['results'][0]['title'].lower() == challenge['name'].lower():
+            print(f"[i] found ID: {challenge['id']}, Name: {challenge['name']}, Difficulty: {challenge['difficulty']}")
+            #print(f"Search response for challenge '{challenge['name']}': {res2}")
+            for attribute in res2['results'][0]['attributes']:
+                if attribute['name'] == "Difficulty":
+                    ea.patch_attribute(attributeId=attribute['attributeId'], value=challenge['difficulty'])
+                if attribute['name'] == "Released":
+                    release_str = challenge['release_date']
+                    release_date = datetime.strptime(release_str, "%Y-%m-%dT%H:%M:%S.%fZ")
+                    formatted_release_date = release_date.strftime("%d %B %Y")
+                    ea.patch_attribute(attributeId=attribute['attributeId'], value=formatted_release_date)
+                if attribute['name'] == "Solved":
+                    if challenge['is_owned']:
+                        ea.patch_attribute(attributeId=attribute['attributeId'], value="done")
+                    else:
+                        ea.patch_attribute(attributeId=attribute['attributeId'], value=" ")
+                if attribute['name'] == "cssClass":
+                    if challenge['is_owned']:
+                        ea.patch_attribute(attributeId=attribute['attributeId'], value="done")
+                    else:
+                        ea.patch_attribute(attributeId=attribute['attributeId'], value="todo")
+
+        else: # doesnt already exist, create page
+            release_str = challenge['release_date']
+            release_date = datetime.strptime(release_str, "%Y-%m-%dT%H:%M:%S.%fZ")
+            formatted_release_date = release_date.strftime("%d %B %Y")
+            new_note = ea.create_note(
+                parentNoteId=catId,
+                type="text",
+                title=challenge['name'],
+                content=" ",
+            )
+            ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="relation", name="template", value=trilium_challenges_template_id)
+            ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="Difficulty", value=challenge['difficulty'])
+            ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="Released", value=formatted_release_date)
+            if challenge['is_owned']:
+                ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="done")
+                ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="Solved", value="done")
+            else:
+                ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="todo")
+                ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="Solved", value=" ")
+
+            print(f"[+] created ID: {challenge['id']}, Name: {challenge['name']}, Difficulty: {challenge['difficulty']}")
+
+print("[+] updating folder name                                          ")
+ea.patch_note(noteId=trilium_challenges_folder,title="Challenges - "+str(total_completed)+" / "+str(len(challenges)))
\ No newline at end of file
diff --git a/htb2trilium_machines.py b/htb2trilium_machines.py
new file mode 100644
index 0000000..de8be53
--- /dev/null
+++ b/htb2trilium_machines.py
@@ -0,0 +1,203 @@
+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>&nbsp;</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)
\ No newline at end of file
diff --git a/htb2trilium_sherlocks.py b/htb2trilium_sherlocks.py
new file mode 100644
index 0000000..e5d9b98
--- /dev/null
+++ b/htb2trilium_sherlocks.py
@@ -0,0 +1,151 @@
+import os
+import json
+import requests
+from collections import defaultdict
+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_sherlocks_folder = config['trilium_sherlocks_folder']
+trilium_sherlocks_template_id = config['trilium_sherlocks_template_id']
+
+def get_timestamp(machine):
+    # Parse the release date string into a datetime object
+    release_str = machine['release_date']
+    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("[+] gathering sherlocks info")
+categories = defaultdict(list)
+sherlocks = client.get_all_sherlocks()
+sherlocks.sort(key=get_timestamp)
+total_completed = 0  # Variable to track total completed sherlocks
+
+print(f"[i] Retrieved {len(sherlocks)} sherlocks")
+
+# Group sherlocks by their categories
+for sherlock in sherlocks:
+    categories[sherlock['category_name']].append(sherlock)
+    if sherlock['progress'] == 100:
+        total_completed += 1  # Increment total completed if challenge is owned
+
+# Print out the grouped sherlocks with the number of completed and total sherlocks in each category
+for category, grouped_sherlocks in categories.items():
+    total = len(grouped_sherlocks)
+    completed = sum(1 for sherlock in grouped_sherlocks if sherlock['progress'] == 100)  # Count completed challenges
+    
+    res = ea.search_note(
+        search=f"note.title %= '{category}*'",
+        ancestorNoteId=trilium_sherlocks_folder,
+        ancestorDepth='eq1',
+        limit=1,
+        fastSearch=True,
+    )
+    
+    catId = ""
+    if res['results'] and res['results'][0]['title'].lower() == category.lower()+" - "+str(completed)+" / "+str(total):
+		# page exists - lets check if the details have changed
+        ea.patch_note(noteId=res['results'][0]['noteId'], title=category+" - "+str(completed)+" / "+str(total))
+        catId = res['results'][0]['noteId']
+        print(f"[i] updated category: {category} - ({completed}/{total})")
+    else:
+        new_note = ea.create_note(
+            parentNoteId=trilium_sherlocks_folder,
+            type="text",
+            title=category+" - "+str(completed)+" / "+str(total),
+            content=" ",
+        )
+        catId = new_note['note']['noteId']
+        print(f"[+] created category: {catId} {category} - ({completed}/{total})")
+    
+    for sherlock in grouped_sherlocks:
+        #print(f" - ID: {challenge['id']}, Name: {challenge['name']}, Difficulty: {challenge['difficulty']}")
+        res2 = ea.search_note(
+            search=f"{sherlock['name']}",
+            ancestorNoteId=catId,
+            ancestorDepth='eq1',
+            limit=1,
+            fastSearch=True,
+        )
+        # already exists update the values
+        if res2['results'] and res2['results'][0]['title'].lower() == sherlock['name'].lower():
+            print(f"[i] found ID: {sherlock['id']}, Name: {sherlock['name']}, Difficulty: {sherlock['difficulty']}")
+    
+            for attribute in res2['results'][0]['attributes']:
+                if attribute['name'] == "Difficulty":
+                    ea.patch_attribute(attributeId=attribute['attributeId'], value=sherlock['difficulty'])
+        
+                if attribute['name'] == "Released":
+                    release_str = sherlock['release_date']
+                    release_date = datetime.strptime(release_str, "%Y-%m-%dT%H:%M:%S.%fZ")
+                    formatted_release_date = release_date.strftime("%d %B %Y")
+                    ea.patch_attribute(attributeId=attribute['attributeId'], value=formatted_release_date)
+        
+                if attribute['name'] == "Solved":
+                    ea.patch_attribute(attributeId=attribute['attributeId'], value=str(sherlock['progress']))
+        
+                if attribute['name'] == "cssClass":
+                    # Determine the cssClass value based on the progress
+                    if sherlock['progress'] == 0:
+                        ea.patch_attribute(attributeId=attribute['attributeId'], value="todo")
+                    elif sherlock['progress'] == 100:
+                        ea.patch_attribute(attributeId=attribute['attributeId'], value="done")
+                    elif 0 < sherlock['progress'] < 100:
+                        ea.patch_attribute(attributeId=attribute['attributeId'], value="inprogress")
+
+
+        else: # doesnt already exist, create page
+            release_str = sherlock['release_date']
+            release_date = datetime.strptime(release_str, "%Y-%m-%dT%H:%M:%S.%fZ")
+            formatted_release_date = release_date.strftime("%d %B %Y")
+            new_note = ea.create_note(
+                parentNoteId=catId,
+                type="text",
+                title=sherlock['name'],
+                content=" ",
+            )
+            ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="relation", name="template", value=trilium_sherlocks_template_id)
+            ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="Difficulty", value=sherlock['difficulty'])
+            ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="Released", value=formatted_release_date)
+            ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="Solved", value=str(sherlock['progress']))
+            if sherlock['progress'] == 0:
+                ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="todo")
+            elif sherlock['progress'] == 100:
+                ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="done")
+            elif 0 < sherlock['progress'] < 100:
+                ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="inprogress")
+
+            print(f"[+] created ID: {sherlock['id']}, Name: {sherlock['name']}, Difficulty: {sherlock['difficulty']}")
+
+print("[+] updating folder name                                          ")
+ea.patch_note(noteId=trilium_sherlocks_folder,title="Sherlocks - "+str(total_completed)+" / "+str(len(sherlocks)))
\ No newline at end of file

diff --git a/INSTALL.md b/INSTALL.md
new file mode 100644
index 0000000..9fe725b
--- /dev/null
+++ b/INSTALL.md
@@ -0,0 +1,32 @@
+## scripts require:
+
+ > pip3 install trilium-py
+
+## config.json
+ - get trilium token in trilium options->ETAPI and "create new token"
+ - create a new note with a title "Machines"
+ - click on this note -> "note info". Note ID is there. This ID goes in "trilium_machines_htb_folder" in the config file
+ - 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" 
+ - 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_machines_template_id"
+
+- create "challenges" page, this page's ID goes in: trilium_challenges_folder  
+- create "Sherlocks" page, this page's ID goes in: trilium_sherlocks_folder  
+- create 2 pages "HTBChallengesTemplate" and "HTBSherlocksTemplate", both of these should have the owned attributes:
+```#template #label:User=promoted,single,text #label:Root=promoted,single,text #label:Tags=promoted,single,text ```
+- The ID's of "HTBChallengesTemplate" and "HTBSherlocksTemplate" go in the matching config values.
+
+ - to get "in progress" todo colour - add to trilium demo/scripting/taskmanager/implementation/CSS:
+
+```
+ span.fancytree-node.inprogress .fancytree-title {
+    color: orange !important;
+ }
+```
\ No newline at end of file
diff --git a/config.json b/config.json
new file mode 100644
index 0000000..39597ce
--- /dev/null
+++ b/config.json
@@ -0,0 +1,11 @@
+{
+	"htb_code": "in https://app.hackthebox.com/profile/settings click 'profile settings' and under 'app tokens' you can 'create app token' - that goes here",
+	"trilium_server_url": "https://place.to.your.trilium.server",
+	"trilium_token": "",
+	"trilium_machines_htb_folder": "",
+	"trilium_machines_template_id": "",
+	"trilium_challenges_folder": "",
+	"trilium_challenges_template_id": "",
+	"trilium_sherlocks_folder": "",
+	"trilium_sherlocks_template_id": ""
+}
\ No newline at end of file
diff --git a/htb2trilium_challenges.py b/htb2trilium_challenges.py
new file mode 100644
index 0000000..0c08b74
--- /dev/null
+++ b/htb2trilium_challenges.py
@@ -0,0 +1,146 @@
+import os
+import json
+import requests
+from collections import defaultdict
+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_challenges_folder = config['trilium_challenges_folder']
+trilium_challenges_template_id = config['trilium_challenges_template_id']
+
+def get_timestamp(machine):
+    # Parse the release date string into a datetime object
+    release_str = machine['release_date']
+    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("[+] gathering challenges info")
+categories = defaultdict(list)
+challenges = client.get_all_challenges()
+challenges.sort(key=get_timestamp)
+total_completed = 0  # Variable to track total completed challenges
+
+print(f"[i] Retrieved {len(challenges)} challenges")
+
+# Group challenges by their categories
+for challenge in challenges:
+    categories[challenge['category_name']].append(challenge)
+    if challenge['is_owned']:
+        total_completed += 1  # Increment total completed if challenge is owned
+
+# Print out the grouped challenges with the number of completed and total challenges in each category
+for category, grouped_challenges in categories.items():
+    total = len(grouped_challenges)
+    completed = sum(1 for challenge in grouped_challenges if challenge['is_owned'])  # Count completed challenges
+    
+    res = ea.search_note(
+        search=f"note.title %= '{category}*'",
+        ancestorNoteId=trilium_challenges_folder,
+        ancestorDepth='eq1',
+        limit=1,
+        fastSearch=True,
+    )
+    
+    catId = ""
+    if res['results'] and res['results'][0]['title'].lower() == category.lower()+" - "+str(completed)+" / "+str(total):
+		# page exists - lets check if the details have changed
+        ea.patch_note(noteId=res['results'][0]['noteId'], title=category+" - "+str(completed)+" / "+str(total))
+        catId = res['results'][0]['noteId']
+        print(f"[i] updated category: {category} - ({completed}/{total})")
+    else:
+        new_note = ea.create_note(
+            parentNoteId=trilium_challenges_folder,
+            type="text",
+            title=category+" - "+str(completed)+" / "+str(total),
+            content=" ",
+        )
+        catId = new_note['note']['noteId']
+        print(f"[+] created category: {catId} {category} - ({completed}/{total})")
+    
+    for challenge in grouped_challenges:
+        #print(f" - ID: {challenge['id']}, Name: {challenge['name']}, Difficulty: {challenge['difficulty']}")
+        res2 = ea.search_note(
+            search=f"{challenge['name']}",
+            ancestorNoteId=catId,
+            ancestorDepth='eq1',
+            limit=1,
+            fastSearch=True,
+        )
+        # already exists update the values
+        if res2['results'] and res2['results'][0]['title'].lower() == challenge['name'].lower():
+            print(f"[i] found ID: {challenge['id']}, Name: {challenge['name']}, Difficulty: {challenge['difficulty']}")
+            #print(f"Search response for challenge '{challenge['name']}': {res2}")
+            for attribute in res2['results'][0]['attributes']:
+                if attribute['name'] == "Difficulty":
+                    ea.patch_attribute(attributeId=attribute['attributeId'], value=challenge['difficulty'])
+                if attribute['name'] == "Released":
+                    release_str = challenge['release_date']
+                    release_date = datetime.strptime(release_str, "%Y-%m-%dT%H:%M:%S.%fZ")
+                    formatted_release_date = release_date.strftime("%d %B %Y")
+                    ea.patch_attribute(attributeId=attribute['attributeId'], value=formatted_release_date)
+                if attribute['name'] == "Solved":
+                    if challenge['is_owned']:
+                        ea.patch_attribute(attributeId=attribute['attributeId'], value="done")
+                    else:
+                        ea.patch_attribute(attributeId=attribute['attributeId'], value=" ")
+                if attribute['name'] == "cssClass":
+                    if challenge['is_owned']:
+                        ea.patch_attribute(attributeId=attribute['attributeId'], value="done")
+                    else:
+                        ea.patch_attribute(attributeId=attribute['attributeId'], value="todo")
+
+        else: # doesnt already exist, create page
+            release_str = challenge['release_date']
+            release_date = datetime.strptime(release_str, "%Y-%m-%dT%H:%M:%S.%fZ")
+            formatted_release_date = release_date.strftime("%d %B %Y")
+            new_note = ea.create_note(
+                parentNoteId=catId,
+                type="text",
+                title=challenge['name'],
+                content=" ",
+            )
+            ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="relation", name="template", value=trilium_challenges_template_id)
+            ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="Difficulty", value=challenge['difficulty'])
+            ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="Released", value=formatted_release_date)
+            if challenge['is_owned']:
+                ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="done")
+                ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="Solved", value="done")
+            else:
+                ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="todo")
+                ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="Solved", value=" ")
+
+            print(f"[+] created ID: {challenge['id']}, Name: {challenge['name']}, Difficulty: {challenge['difficulty']}")
+
+print("[+] updating folder name                                          ")
+ea.patch_note(noteId=trilium_challenges_folder,title="Challenges - "+str(total_completed)+" / "+str(len(challenges)))
\ No newline at end of file
diff --git a/htb2trilium_machines.py b/htb2trilium_machines.py
new file mode 100644
index 0000000..de8be53
--- /dev/null
+++ b/htb2trilium_machines.py
@@ -0,0 +1,203 @@
+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>&nbsp;</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)
\ No newline at end of file
diff --git a/htb2trilium_sherlocks.py b/htb2trilium_sherlocks.py
new file mode 100644
index 0000000..e5d9b98
--- /dev/null
+++ b/htb2trilium_sherlocks.py
@@ -0,0 +1,151 @@
+import os
+import json
+import requests
+from collections import defaultdict
+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_sherlocks_folder = config['trilium_sherlocks_folder']
+trilium_sherlocks_template_id = config['trilium_sherlocks_template_id']
+
+def get_timestamp(machine):
+    # Parse the release date string into a datetime object
+    release_str = machine['release_date']
+    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("[+] gathering sherlocks info")
+categories = defaultdict(list)
+sherlocks = client.get_all_sherlocks()
+sherlocks.sort(key=get_timestamp)
+total_completed = 0  # Variable to track total completed sherlocks
+
+print(f"[i] Retrieved {len(sherlocks)} sherlocks")
+
+# Group sherlocks by their categories
+for sherlock in sherlocks:
+    categories[sherlock['category_name']].append(sherlock)
+    if sherlock['progress'] == 100:
+        total_completed += 1  # Increment total completed if challenge is owned
+
+# Print out the grouped sherlocks with the number of completed and total sherlocks in each category
+for category, grouped_sherlocks in categories.items():
+    total = len(grouped_sherlocks)
+    completed = sum(1 for sherlock in grouped_sherlocks if sherlock['progress'] == 100)  # Count completed challenges
+    
+    res = ea.search_note(
+        search=f"note.title %= '{category}*'",
+        ancestorNoteId=trilium_sherlocks_folder,
+        ancestorDepth='eq1',
+        limit=1,
+        fastSearch=True,
+    )
+    
+    catId = ""
+    if res['results'] and res['results'][0]['title'].lower() == category.lower()+" - "+str(completed)+" / "+str(total):
+		# page exists - lets check if the details have changed
+        ea.patch_note(noteId=res['results'][0]['noteId'], title=category+" - "+str(completed)+" / "+str(total))
+        catId = res['results'][0]['noteId']
+        print(f"[i] updated category: {category} - ({completed}/{total})")
+    else:
+        new_note = ea.create_note(
+            parentNoteId=trilium_sherlocks_folder,
+            type="text",
+            title=category+" - "+str(completed)+" / "+str(total),
+            content=" ",
+        )
+        catId = new_note['note']['noteId']
+        print(f"[+] created category: {catId} {category} - ({completed}/{total})")
+    
+    for sherlock in grouped_sherlocks:
+        #print(f" - ID: {challenge['id']}, Name: {challenge['name']}, Difficulty: {challenge['difficulty']}")
+        res2 = ea.search_note(
+            search=f"{sherlock['name']}",
+            ancestorNoteId=catId,
+            ancestorDepth='eq1',
+            limit=1,
+            fastSearch=True,
+        )
+        # already exists update the values
+        if res2['results'] and res2['results'][0]['title'].lower() == sherlock['name'].lower():
+            print(f"[i] found ID: {sherlock['id']}, Name: {sherlock['name']}, Difficulty: {sherlock['difficulty']}")
+    
+            for attribute in res2['results'][0]['attributes']:
+                if attribute['name'] == "Difficulty":
+                    ea.patch_attribute(attributeId=attribute['attributeId'], value=sherlock['difficulty'])
+        
+                if attribute['name'] == "Released":
+                    release_str = sherlock['release_date']
+                    release_date = datetime.strptime(release_str, "%Y-%m-%dT%H:%M:%S.%fZ")
+                    formatted_release_date = release_date.strftime("%d %B %Y")
+                    ea.patch_attribute(attributeId=attribute['attributeId'], value=formatted_release_date)
+        
+                if attribute['name'] == "Solved":
+                    ea.patch_attribute(attributeId=attribute['attributeId'], value=str(sherlock['progress']))
+        
+                if attribute['name'] == "cssClass":
+                    # Determine the cssClass value based on the progress
+                    if sherlock['progress'] == 0:
+                        ea.patch_attribute(attributeId=attribute['attributeId'], value="todo")
+                    elif sherlock['progress'] == 100:
+                        ea.patch_attribute(attributeId=attribute['attributeId'], value="done")
+                    elif 0 < sherlock['progress'] < 100:
+                        ea.patch_attribute(attributeId=attribute['attributeId'], value="inprogress")
+
+
+        else: # doesnt already exist, create page
+            release_str = sherlock['release_date']
+            release_date = datetime.strptime(release_str, "%Y-%m-%dT%H:%M:%S.%fZ")
+            formatted_release_date = release_date.strftime("%d %B %Y")
+            new_note = ea.create_note(
+                parentNoteId=catId,
+                type="text",
+                title=sherlock['name'],
+                content=" ",
+            )
+            ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="relation", name="template", value=trilium_sherlocks_template_id)
+            ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="Difficulty", value=sherlock['difficulty'])
+            ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="Released", value=formatted_release_date)
+            ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="Solved", value=str(sherlock['progress']))
+            if sherlock['progress'] == 0:
+                ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="todo")
+            elif sherlock['progress'] == 100:
+                ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="done")
+            elif 0 < sherlock['progress'] < 100:
+                ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="inprogress")
+
+            print(f"[+] created ID: {sherlock['id']}, Name: {sherlock['name']}, Difficulty: {sherlock['difficulty']}")
+
+print("[+] updating folder name                                          ")
+ea.patch_note(noteId=trilium_sherlocks_folder,title="Sherlocks - "+str(total_completed)+" / "+str(len(sherlocks)))
\ No newline at end of file
diff --git a/htb_client.py b/htb_client.py
new file mode 100644
index 0000000..a35929a
--- /dev/null
+++ b/htb_client.py
@@ -0,0 +1,214 @@
+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
+
+    def get_all_challenges(self):
+        challenges = []
+        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}/challenges?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 challenges: {response.status_code}, {response.text}")
+
+            data = response.json()
+            for challenge in data['data']:
+                if challenge['id'] not in seen_ids and challenge['name'] not in seen_names:
+                    challenges.append(challenge)
+                    seen_ids.add(challenge['id'])
+                    seen_names.add(challenge['name'])
+
+            # Check for pagination
+            if page >= data['meta']['last_page']:
+                break
+            page += 1
+        
+        return challenges
+
+    def get_all_sherlocks(self):
+        sherlocks = []
+        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}/sherlocks?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 sherlocks: {response.status_code}, {response.text}")
+
+            data = response.json()
+            for sherlock in data['data']:
+                if sherlock['id'] not in seen_ids and sherlock['name'] not in seen_names:
+                    sherlocks.append(sherlock)
+                    seen_ids.add(sherlock['id'])
+                    seen_names.add(sherlock['name'])
+
+            # Check for pagination
+            if page >= data['meta']['last_page']:
+                break
+            page += 1
+        
+        return sherlocks
\ No newline at end of file