Newer
Older
htb2trilium / htb2trilium_machines.py
0xRoM on 6 Oct 8 KB initial commit
  1. import os
  2. import json
  3. import requests
  4. from bs4 import BeautifulSoup
  5. from datetime import datetime, timezone
  6. from htb_client import HTBClient
  7. from trilium_py.client import ETAPI
  8.  
  9. # Get the absolute path of the script's directory
  10. script_dir = os.path.dirname(os.path.abspath(__file__))
  11.  
  12. # Construct the full path to the config.json file
  13. config_path = os.path.join(script_dir, 'config.json')
  14.  
  15. # Load configuration from the JSON file
  16. with open(config_path, 'r') as f:
  17. config = json.load(f)
  18.  
  19. # Accessing config values
  20. htb_code = config['htb_code']
  21. trilium_server_url = config['trilium_server_url']
  22. trilium_token = config['trilium_token']
  23. trilium_machines_htb_folder = config['trilium_machines_htb_folder']
  24. trilium_machines_template_id = config['trilium_machines_template_id']
  25.  
  26. def generate_newpage(machine):
  27. no = "<span style=\"color:hsl(0,75%,60%);\">No</span>"
  28. yes = "<span style=\"color:hsl(120,75%,60%);\">Yes</span>"
  29. user_colour = no
  30. if machine['authUserInUserOwns']:
  31. user_colour = yes
  32. root_colour = no
  33. if machine['authUserInRootOwns']:
  34. root_colour = yes
  35.  
  36. status = "Retired"
  37. if machine['active']:
  38. status = "Active"
  39.  
  40. release_str = machine['release']
  41. release_date = datetime.strptime(release_str, "%Y-%m-%dT%H:%M:%S.%fZ")
  42. formatted_release_date = release_date.strftime("%d %B %Y")
  43.  
  44. html = """
  45. <figure class="image image-style-align-left image_resized" style="width:12.28%;">
  46. <img src="https://www.hackthebox.com{avatar}">
  47. </figure>
  48. <p>
  49. <strong>OS:</strong> {os}<br>
  50. <strong>Difficulty:</strong> {difficultyText} <br>
  51. <strong>Rating:</strong> {rating} / 5<br>
  52. <strong>Points:</strong> {points}<br>
  53. <strong>User / Root: </strong> {user_colour} / {root_colour}<br>
  54. <strong>Released:</strong> {release_date}<br>
  55. <strong>State:</strong> {status}
  56. </p>
  57. <hr>
  58. <h2>Notes</h2>
  59. <p>&nbsp;</p>
  60. """.format(
  61. os=machine['os'],
  62. difficultyText=machine['difficultyText'],
  63. rating=machine['star'],
  64. points=machine['points'],
  65. release_date=formatted_release_date,
  66. user_colour=user_colour,
  67. root_colour=root_colour,
  68. status = status,
  69. avatar = machine['avatar'],
  70. )
  71.  
  72. return html
  73.  
  74. def get_timestamp(machine):
  75. # Parse the release date string into a datetime object
  76. release_str = machine['release']
  77. dt = datetime.strptime(release_str, "%Y-%m-%dT%H:%M:%S.%fZ")
  78. # Set the timezone to UTC
  79. dt = dt.replace(tzinfo=timezone.utc)
  80. # Get the timestamp
  81. timestamp = dt.timestamp()
  82. return timestamp
  83.  
  84. print("[+] connecting to HTB")
  85. client = HTBClient(password=htb_code)
  86. print("[+] connecting to trilium")
  87. ea = ETAPI(trilium_server_url, trilium_token)
  88. print("[i] version: ", ea.app_info()['appVersion'])
  89.  
  90. print("[i] HTB User:", client.user['id'], "-", client.user['name'])
  91. print("[i] user owns:", client.user['user_owns'], "| Root owns:", client.user['root_owns'], "| Respect:", client.user['respects'])
  92.  
  93. master_folder = ea.get_note(trilium_machines_htb_folder)
  94. for attribute in master_folder['attributes']:
  95. if attribute['name'] == "user":
  96. if attribute['value'] != str(client.user['user_owns']):
  97. print("[+] updating user owns (folder attribute)")
  98. ea.patch_attribute(attributeId=attribute['attributeId'], value=str(client.user['user_owns']))
  99. if attribute['name'] == "root":
  100. if attribute['value'] != str(client.user['root_owns']):
  101. print("[+] updating root owns (folder attribute)")
  102. ea.patch_attribute(attributeId=attribute['attributeId'], value=str(client.user['root_owns']))
  103. if attribute['name'] == "respect":
  104. if attribute['value'] != str(client.user['respects']):
  105. print("[+] updating respect (folder attribute)")
  106. ea.patch_attribute(attributeId=attribute['attributeId'], value=str(client.user['respects']))
  107.  
  108. print("[+] gathering machines info")
  109. machines = client.get_all_machines()
  110. machines.sort(key=get_timestamp)
  111.  
  112. print(f"[i] Retrieved {len(machines)} machines")
  113. #for machine in machines:
  114. # print(f" - ID: {machine['id']}, Name: {machine['name']}, OS: {machine['os']}, Difficulty: {machine['difficultyText']}")
  115.  
  116. machine_count = 0
  117. completed_count = 0
  118. for machine in machines:
  119. machine_count += 1
  120. print('processing: ',machine_count, "/", len(machines), "("+machine['name']+") " , end='\r')
  121.  
  122. res = ea.search_note(
  123. search="\""+machine['name']+"\"",
  124. ancestorNoteId=trilium_machines_htb_folder,
  125. ancestorDepth='eq1',
  126. limit=1,
  127. fastSearch=True,
  128. )
  129.  
  130. if res['results'] and machine['name'] == res['results'][0]['title']:
  131. # page exists - lets check if the details have changed
  132.  
  133. current_html = ea.get_note_content(noteId=res['results'][0]['noteId'])
  134. current_soup = BeautifulSoup(current_html, 'html.parser')
  135. current_paragraph = current_soup.find_all('p')[0].text
  136.  
  137. new_html = generate_newpage(machine)
  138. new_soup = BeautifulSoup(new_html, 'html.parser')
  139. new_paragraph = new_soup.find_all('p')[0].text
  140.  
  141. # current page contains first paragraph of "blank" (useful for when it doesnt create or find the note properly.. shouldnt get here)
  142. if current_paragraph == "blank":
  143. ea.update_note_content(noteId=res['results'][0]['noteId'], content=new_html)
  144. ea.create_attribute(attributeId=None, isInheritable=False, noteId=res['results'][0]['noteId'], type="relation", name="template", value=trilium_machines_htb_folder)
  145. if machine['authUserInUserOwns'] and machine['authUserInRootOwns']:
  146. ea.create_attribute(attributeId=None, isInheritable=False, noteId=res['results'][0]['noteId'], type="label", name="cssClass", value="done")
  147. else:
  148. if machine['authUserInUserOwns'] or machine['authUserInRootOwns']:
  149. ea.create_attribute(attributeId=None, isInheritable=False, noteId=res['results'][0]['noteId'], type="label", name="cssClass", value="inprogress")
  150. else:
  151. ea.create_attribute(attributeId=None, isInheritable=False, noteId=res['results'][0]['noteId'], type="label", name="cssClass", value="todo")
  152. # re-get the current content
  153. current_html = ea.get_note_content(noteId=res['results'][0]['noteId'])
  154. current_soup = BeautifulSoup(current_html, 'html.parser')
  155.  
  156. if current_paragraph != new_paragraph:
  157.  
  158. # details have updated!
  159. print("[+] updating page:",machine['name'], "-> "+res['results'][0]['title']+" ")
  160. replacement = current_soup.find('p')
  161. replacement.replace_with( new_soup.find_all('p')[0] )
  162. ea.update_note_content(noteId=res['results'][0]['noteId'], content=current_soup)
  163.  
  164. # now to update the label
  165. for attribute in res['results'][0]['attributes']:
  166. if attribute['name'] == "cssClass":
  167. if machine['authUserInUserOwns'] and machine['authUserInRootOwns']:
  168. ea.patch_attribute(attributeId=attribute['attributeId'], value="done")
  169. else:
  170. if machine['authUserInUserOwns'] or machine['authUserInRootOwns']:
  171. ea.patch_attribute(attributeId=attribute['attributeId'], value="inprogress")
  172. else:
  173. ea.patch_attribute(attributeId=attribute['attributeId'], value="todo")
  174. else:
  175. # title does not exist - create the note
  176. html = generate_newpage(machine)
  177. new_note = ea.create_note(
  178. parentNoteId=trilium_machines_htb_folder,
  179. type="text",
  180. title=machine['name'],
  181. content=html,
  182. )
  183. print("[+] created note:", machine['name'], " ")
  184.  
  185. ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="relation", name="template", value=trilium_machines_htb_folder)
  186. if machine['authUserInUserOwns'] and machine['authUserInRootOwns']:
  187. ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="done")
  188. else:
  189. if machine['authUserInUserOwns'] or machine['authUserInRootOwns']:
  190. ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="inprogress")
  191. else:
  192. ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="todo")
  193.  
  194. if machine['authUserInUserOwns'] and machine['authUserInRootOwns']:
  195. completed_count += 1
  196.  
  197. print("[+] updating folder name ")
  198. ea.patch_note(noteId=trilium_machines_htb_folder,title="Machines - "+str(completed_count)+" / "+str(len(machines)))
  199.  
  200. print("[=] processed", machine_count)
Buy Me A Coffee