Newer
Older
htb2trilium / htb2trilium_challenges.py
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'].split(' - ')[0].strip().lower() == category.lower():
        # 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)))