Newer
Older
htb2trilium / htb2trilium_challenges.py
  1. import os
  2. import json
  3. import requests
  4. from collections import defaultdict
  5. from bs4 import BeautifulSoup
  6. from datetime import datetime, timezone
  7. from htb_client import HTBClient
  8. from trilium_py.client import ETAPI
  9.  
  10. # Get the absolute path of the script's directory
  11. script_dir = os.path.dirname(os.path.abspath(__file__))
  12.  
  13. # Construct the full path to the config.json file
  14. config_path = os.path.join(script_dir, 'config.json')
  15.  
  16. # Load configuration from the JSON file
  17. with open(config_path, 'r') as f:
  18. config = json.load(f)
  19.  
  20. # Accessing config values
  21. htb_code = config['htb_code']
  22. trilium_server_url = config['trilium_server_url']
  23. trilium_token = config['trilium_token']
  24. trilium_challenges_folder = config['trilium_challenges_folder']
  25. trilium_challenges_template_id = config['trilium_challenges_template_id']
  26.  
  27. def get_timestamp(machine):
  28. # Parse the release date string into a datetime object
  29. release_str = machine['release_date']
  30. dt = datetime.strptime(release_str, "%Y-%m-%dT%H:%M:%S.%fZ")
  31. # Set the timezone to UTC
  32. dt = dt.replace(tzinfo=timezone.utc)
  33. # Get the timestamp
  34. timestamp = dt.timestamp()
  35. return timestamp
  36.  
  37. print("[+] connecting to HTB")
  38. client = HTBClient(password=htb_code)
  39. print("[+] connecting to trilium")
  40. ea = ETAPI(trilium_server_url, trilium_token)
  41. print("[i] version: ", ea.app_info()['appVersion'])
  42.  
  43. print("[i] HTB User:", client.user['id'], "-", client.user['name'])
  44.  
  45.  
  46. print("[+] gathering challenges info")
  47. categories = defaultdict(list)
  48. challenges = client.get_all_challenges()
  49. challenges.sort(key=get_timestamp)
  50. total_completed = 0 # Variable to track total completed challenges
  51.  
  52. print(f"[i] Retrieved {len(challenges)} challenges")
  53.  
  54. # Group challenges by their categories
  55. for challenge in challenges:
  56. categories[challenge['category_name']].append(challenge)
  57. if challenge['is_owned']:
  58. total_completed += 1 # Increment total completed if challenge is owned
  59.  
  60. # Print out the grouped challenges with the number of completed and total challenges in each category
  61. for category, grouped_challenges in categories.items():
  62. total = len(grouped_challenges)
  63. completed = sum(1 for challenge in grouped_challenges if challenge['is_owned']) # Count completed challenges
  64. res = ea.search_note(
  65. search=f"note.title %= '{category}*'",
  66. ancestorNoteId=trilium_challenges_folder,
  67. ancestorDepth='eq1',
  68. limit=1,
  69. fastSearch=True,
  70. )
  71. catId = ""
  72. if res['results'] and res['results'][0]['title'].split(' - ')[0].strip().lower() == category.lower():
  73. # page exists - lets check if the details have changed
  74. ea.patch_note(noteId=res['results'][0]['noteId'], title=category+" - "+str(completed)+" / "+str(total))
  75. catId = res['results'][0]['noteId']
  76. print(f"[i] updated category: {category} - ({completed}/{total})")
  77. else:
  78. new_note = ea.create_note(
  79. parentNoteId=trilium_challenges_folder,
  80. type="text",
  81. title=category+" - "+str(completed)+" / "+str(total),
  82. content=" ",
  83. )
  84. catId = new_note['note']['noteId']
  85. print(f"[+] created category: {catId} {category} - ({completed}/{total})")
  86. for challenge in grouped_challenges:
  87. #print(f" - ID: {challenge['id']}, Name: {challenge['name']}, Difficulty: {challenge['difficulty']}")
  88. res2 = ea.search_note(
  89. search=f"{challenge['name']}",
  90. ancestorNoteId=catId,
  91. ancestorDepth='eq1',
  92. limit=1,
  93. fastSearch=True,
  94. )
  95. # already exists update the values
  96. if res2['results'] and res2['results'][0]['title'].lower() == challenge['name'].lower():
  97. print(f"[i] found ID: {challenge['id']}, Name: {challenge['name']}, Difficulty: {challenge['difficulty']}")
  98. #print(f"Search response for challenge '{challenge['name']}': {res2}")
  99. for attribute in res2['results'][0]['attributes']:
  100. if attribute['name'] == "Difficulty":
  101. ea.patch_attribute(attributeId=attribute['attributeId'], value=challenge['difficulty'])
  102. if attribute['name'] == "Released":
  103. release_str = challenge['release_date']
  104. release_date = datetime.strptime(release_str, "%Y-%m-%dT%H:%M:%S.%fZ")
  105. formatted_release_date = release_date.strftime("%d %B %Y")
  106. ea.patch_attribute(attributeId=attribute['attributeId'], value=formatted_release_date)
  107. if attribute['name'] == "Solved":
  108. if challenge['is_owned']:
  109. ea.patch_attribute(attributeId=attribute['attributeId'], value="done")
  110. else:
  111. ea.patch_attribute(attributeId=attribute['attributeId'], value=" ")
  112. if attribute['name'] == "cssClass":
  113. if challenge['is_owned']:
  114. ea.patch_attribute(attributeId=attribute['attributeId'], value="done")
  115. else:
  116. ea.patch_attribute(attributeId=attribute['attributeId'], value="todo")
  117.  
  118. else: # doesnt already exist, create page
  119. release_str = challenge['release_date']
  120. release_date = datetime.strptime(release_str, "%Y-%m-%dT%H:%M:%S.%fZ")
  121. formatted_release_date = release_date.strftime("%d %B %Y")
  122. new_note = ea.create_note(
  123. parentNoteId=catId,
  124. type="text",
  125. title=challenge['name'],
  126. content=" ",
  127. )
  128. ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="relation", name="template", value=trilium_challenges_template_id)
  129. ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="Difficulty", value=challenge['difficulty'])
  130. ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="Released", value=formatted_release_date)
  131. if challenge['is_owned']:
  132. ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="done")
  133. ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="Solved", value="done")
  134. else:
  135. ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="todo")
  136. ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="Solved", value=" ")
  137.  
  138. print(f"[+] created ID: {challenge['id']}, Name: {challenge['name']}, Difficulty: {challenge['difficulty']}")
  139.  
  140. print("[+] updating folder name ")
  141. ea.patch_note(noteId=trilium_challenges_folder,title="Challenges - "+str(total_completed)+" / "+str(len(challenges)))
Buy Me A Coffee