diff --git a/htb2trilium/htb2trilium_challenges.py b/htb2trilium/htb2trilium_challenges.py index d0d95ca..4babcba 100644 --- a/htb2trilium/htb2trilium_challenges.py +++ b/htb2trilium/htb2trilium_challenges.py @@ -1,6 +1,8 @@ import os import json import requests +import re +import unicodedata from collections import defaultdict from bs4 import BeautifulSoup from datetime import datetime, timezone @@ -53,6 +55,15 @@ print(f"[i] Retrieved {len(challenges)} challenges") +def normalise_title(title): + # Strip accents, smart quotes, etc. + title = unicodedata.normalize("NFKD", title) + # Replace fancy punctuation + title = title.replace("’", "'").replace("`", "'").replace("–", "-").replace("“", '"').replace("”", '"') + # Remove excess whitespace + title = re.sub(r'\s+', ' ', title) + return title.strip().lower() + # Group challenges by their categories for challenge in challenges: categories[challenge['category_name']].append(challenge) @@ -65,7 +76,7 @@ completed = sum(1 for challenge in grouped_challenges if challenge['is_owned']) # Count completed challenges res = ea.search_note( - search=f"note.title %= '{category}*'", + search=f"note.title %= '^{category}*'", ancestorNoteId=trilium_challenges_folder, ancestorDepth='eq1', limit=1, @@ -73,52 +84,62 @@ ) 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})") + if res['results']: + matched_note = res['results'][0] + existing_category = " - ".join(matched_note['title'].split(" - ")[:-1]) # Extract category portion + if existing_category.strip().lower() == category.lower(): + ea.patch_note( + noteId=matched_note['noteId'], + title=f"{category} - {completed} / {total}" + ) + catId = matched_note['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), + title=f"{category} - {completed} / {total}", content=" ", ) catId = new_note['note']['noteId'] - print(f"[+] created category: {catId} {category} - ({completed}/{total})") + print(f"[+] Created category: {catId} {category} - ({completed}/{total})") for challenge in grouped_challenges: #print(f" - ID: {challenge['id']}, Name: {challenge['name']}, Difficulty: {challenge['difficulty']}") + escaped_name = challenge['name'].replace("'", "\\'").replace(",", "\\,") res2 = ea.search_note( - search=f"{challenge['name']}", + search=f"note.title = '{escaped_name}'", ancestorNoteId=catId, ancestorDepth='eq1', + orderBy=["title"], 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") + + if res2['results']: + note_title = normalise_title(res2['results'][0]['title']) + challenge_title = normalise_title(challenge['name']) + + if note_title == challenge_title: + + 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'] diff --git a/htb2trilium/htb2trilium_challenges.py b/htb2trilium/htb2trilium_challenges.py index d0d95ca..4babcba 100644 --- a/htb2trilium/htb2trilium_challenges.py +++ b/htb2trilium/htb2trilium_challenges.py @@ -1,6 +1,8 @@ import os import json import requests +import re +import unicodedata from collections import defaultdict from bs4 import BeautifulSoup from datetime import datetime, timezone @@ -53,6 +55,15 @@ print(f"[i] Retrieved {len(challenges)} challenges") +def normalise_title(title): + # Strip accents, smart quotes, etc. + title = unicodedata.normalize("NFKD", title) + # Replace fancy punctuation + title = title.replace("’", "'").replace("`", "'").replace("–", "-").replace("“", '"').replace("”", '"') + # Remove excess whitespace + title = re.sub(r'\s+', ' ', title) + return title.strip().lower() + # Group challenges by their categories for challenge in challenges: categories[challenge['category_name']].append(challenge) @@ -65,7 +76,7 @@ completed = sum(1 for challenge in grouped_challenges if challenge['is_owned']) # Count completed challenges res = ea.search_note( - search=f"note.title %= '{category}*'", + search=f"note.title %= '^{category}*'", ancestorNoteId=trilium_challenges_folder, ancestorDepth='eq1', limit=1, @@ -73,52 +84,62 @@ ) 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})") + if res['results']: + matched_note = res['results'][0] + existing_category = " - ".join(matched_note['title'].split(" - ")[:-1]) # Extract category portion + if existing_category.strip().lower() == category.lower(): + ea.patch_note( + noteId=matched_note['noteId'], + title=f"{category} - {completed} / {total}" + ) + catId = matched_note['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), + title=f"{category} - {completed} / {total}", content=" ", ) catId = new_note['note']['noteId'] - print(f"[+] created category: {catId} {category} - ({completed}/{total})") + print(f"[+] Created category: {catId} {category} - ({completed}/{total})") for challenge in grouped_challenges: #print(f" - ID: {challenge['id']}, Name: {challenge['name']}, Difficulty: {challenge['difficulty']}") + escaped_name = challenge['name'].replace("'", "\\'").replace(",", "\\,") res2 = ea.search_note( - search=f"{challenge['name']}", + search=f"note.title = '{escaped_name}'", ancestorNoteId=catId, ancestorDepth='eq1', + orderBy=["title"], 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") + + if res2['results']: + note_title = normalise_title(res2['results'][0]['title']) + challenge_title = normalise_title(challenge['name']) + + if note_title == challenge_title: + + 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'] diff --git a/htb2trilium/htb2trilium_machines.py b/htb2trilium/htb2trilium_machines.py index de8be53..3535fab 100644 --- a/htb2trilium/htb2trilium_machines.py +++ b/htb2trilium/htb2trilium_machines.py @@ -121,15 +121,15 @@ machine_count += 1 print('processing: ',machine_count, "/", len(machines), "("+machine['name']+") " , end='\r') + escaped_title = machine['name'].replace("'", "\\'") res = ea.search_note( - search="\""+machine['name']+"\"", - ancestorNoteId=trilium_machines_htb_folder, - ancestorDepth='eq1', - limit=1, - fastSearch=True, + search=f"note.title = '{escaped_title}'", + ancestorNoteId=trilium_machines_htb_folder, + ancestorDepth='eq1', + limit=1, ) - if res['results'] and machine['name'] == res['results'][0]['title']: + if res['results'] and machine['name'].strip().lower() == res['results'][0]['title'].strip().lower(): # page exists - lets check if the details have changed current_html = ea.get_note_content(noteId=res['results'][0]['noteId'])