diff --git a/README.md b/README.md
index f623b41..fefccb6 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,58 @@
TriliumScripts
===============
-My collection of scripts for use with trilium
\ No newline at end of file
+Collection of scripts for use with trilium
+
+## syntax highlighting
+
+**highlight_moodlog.js** & **highlight_todo.js** - Both of these will apply appropriate syntaxy highlighting to the specific note.
+
+### installation
+
+1) add the code as the notes contents.
+
+2) set "note type" to "JS frontend"
+
+3) add an "owned attribute" of "#widget"
+
+### use
+
+on the note you want highlighting set the "owned attributes" to "#todotxt #hideHighlightWidget" or "#moodlog #hideHighlightWidget" depending on which syntax type you want to highlight
+
+## bidirectional sync
+
+**trilium_sync.php** - script to keep a folder of local text files and a trilium note in sync.
+
+### installation
+
+Edit the config values at the top of the script to match your trilium setup. Run it manually to test it is working as expected. If it is create a cron job.
+
+### example use
+
+```
+$> php trilium_sync.php
+[i] checking connections
+[i] version: x.xx.x
+[i] gathering info
+
+- Folders -
+Folder/Path Folder Name Parent noteId Mod Trl Mod Dsk Status
+Synced/ Synced root j0DRZhIzV97W 1742502936 1742507367 Exists
+Synced/BothPlaces/ BothPlaces j0DRZhIzV97W AQb3Dg0yZHSu 1742507075 1742507200 Exists
+Synced/[del] tobe del/ [del] tobe del j0DRZhIzV97W j0xUs1FdZL1Y 1742507321 Missing_Disk
+Synced/AddToTrilium/ AddToTrilium 1742507383 Missing_Trilium
+Synced/tobe deleted/ tobe deleted 1742507200 Missing_Trilium
+- Files -
+Folder/Path Filename Parent noteId Mod Trl Mod Dsk Hash Status
+Synced/BothPlaces/ UpdateDiskFile AQb3Dg0yZHSu IwUZLwWdbiWG 1742507314 1742507199 Diff Update_Disk
+Synced/BothPlaces/ UpdateTriliumFile AQb3Dg0yZHSu 7pMPgsb9AVRf 1742507130 1742507345 Diff Update_Trilium
+Synced/[del] tobe del/ gone_also.txt j0xUs1FdZL1Y 4wOoHDy07csr 1742507178 N/A Missing_Disk
+Synced/AddToTrilium/ fromDiskToTrilium.txt 1742507394 N/A Missing_Trilium
+Synced/tobe deleted/ gone_also.txt 1742507200 N/A Missing_Trilium
+
+[-] deleting disk folder: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/tobe deleted/
+[+] creating trilium folder: Synced/AddToTrilium/
+[+] creating trilium note: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/AddToTrilium/fromDiskToTrilium.txt
+[+] updating file on disk: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/BothPlaces/UpdateDiskFile
+[+] updating file on trilium: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/BothPlaces/UpdateTriliumFile
+```
\ No newline at end of file
diff --git a/README.md b/README.md
index f623b41..fefccb6 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,58 @@
TriliumScripts
===============
-My collection of scripts for use with trilium
\ No newline at end of file
+Collection of scripts for use with trilium
+
+## syntax highlighting
+
+**highlight_moodlog.js** & **highlight_todo.js** - Both of these will apply appropriate syntaxy highlighting to the specific note.
+
+### installation
+
+1) add the code as the notes contents.
+
+2) set "note type" to "JS frontend"
+
+3) add an "owned attribute" of "#widget"
+
+### use
+
+on the note you want highlighting set the "owned attributes" to "#todotxt #hideHighlightWidget" or "#moodlog #hideHighlightWidget" depending on which syntax type you want to highlight
+
+## bidirectional sync
+
+**trilium_sync.php** - script to keep a folder of local text files and a trilium note in sync.
+
+### installation
+
+Edit the config values at the top of the script to match your trilium setup. Run it manually to test it is working as expected. If it is create a cron job.
+
+### example use
+
+```
+$> php trilium_sync.php
+[i] checking connections
+[i] version: x.xx.x
+[i] gathering info
+
+- Folders -
+Folder/Path Folder Name Parent noteId Mod Trl Mod Dsk Status
+Synced/ Synced root j0DRZhIzV97W 1742502936 1742507367 Exists
+Synced/BothPlaces/ BothPlaces j0DRZhIzV97W AQb3Dg0yZHSu 1742507075 1742507200 Exists
+Synced/[del] tobe del/ [del] tobe del j0DRZhIzV97W j0xUs1FdZL1Y 1742507321 Missing_Disk
+Synced/AddToTrilium/ AddToTrilium 1742507383 Missing_Trilium
+Synced/tobe deleted/ tobe deleted 1742507200 Missing_Trilium
+- Files -
+Folder/Path Filename Parent noteId Mod Trl Mod Dsk Hash Status
+Synced/BothPlaces/ UpdateDiskFile AQb3Dg0yZHSu IwUZLwWdbiWG 1742507314 1742507199 Diff Update_Disk
+Synced/BothPlaces/ UpdateTriliumFile AQb3Dg0yZHSu 7pMPgsb9AVRf 1742507130 1742507345 Diff Update_Trilium
+Synced/[del] tobe del/ gone_also.txt j0xUs1FdZL1Y 4wOoHDy07csr 1742507178 N/A Missing_Disk
+Synced/AddToTrilium/ fromDiskToTrilium.txt 1742507394 N/A Missing_Trilium
+Synced/tobe deleted/ gone_also.txt 1742507200 N/A Missing_Trilium
+
+[-] deleting disk folder: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/tobe deleted/
+[+] creating trilium folder: Synced/AddToTrilium/
+[+] creating trilium note: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/AddToTrilium/fromDiskToTrilium.txt
+[+] updating file on disk: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/BothPlaces/UpdateDiskFile
+[+] updating file on trilium: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/BothPlaces/UpdateTriliumFile
+```
\ No newline at end of file
diff --git a/highlight_moodlog.js b/highlight_moodlog.js
new file mode 100644
index 0000000..be4c283
--- /dev/null
+++ b/highlight_moodlog.js
@@ -0,0 +1,165 @@
+const TPL = `
+ Moodlog Highlighting Applied
+
`;
+
+class MoodlogHighlighterWidget extends api.NoteContextAwareWidget {
+ static get parentWidget() {
+ //console.log("MDLG: Getting parent widget: center-pane");
+ return 'center-pane';
+ }
+
+ get position() {
+ //console.log("MDLG: Setting position: 100");
+ return 100;
+ }
+
+ isEnabled() {
+ const enabled = super.isEnabled()
+ && this.note.type === 'text'
+ && this.note.hasLabel('moodlog');
+ //console.log(`MDLG: isEnabled: ${enabled}`);
+ return enabled;
+ }
+
+ doRender() {
+ //console.log("MDLG: Rendering widget...");
+ this.$widget = $(TPL);
+ //console.log("MDLG: Widget rendered:", this.$widget);
+ return this.$widget;
+ }
+
+ async refreshWithNote(note) {
+ //console.log("MDLG: Refreshing with note:", note);
+ const { content } = await note.getNoteComplement();
+ //console.log("MDLG: Note content fetched:", content);
+
+ // Check if content is fetched properly
+ if (!content) {
+ //console.log("MDLG: No content fetched. Exiting refresh.");
+ return;
+ }
+
+ const highlightedContent = this.applyHighlighting(content);
+ //console.log("MDLG: Highlighted content:", highlightedContent);
+
+ if (highlightedContent !== content) {
+ //console.log("MDLG: Updating note content with highlighted version");
+
+ // Use the setNoteContent function to update the note content
+ try {
+ await setNoteContent(note.noteId, highlightedContent);
+ //console.log("MDLG: Content updated with highlighted version");
+ } catch (error) {
+ //console.error("MDLG: Error updating content:", error);
+ }
+ } else {
+ //console.log("MDLG: No changes to content, skipping update.");
+ }
+ }
+
+
+ applyHighlighting(content) {
+ //console.log("MDLG: Applying highlighting to content...");
+
+ // Replace with \n, then remove all other HTML tags
+ const plainTextContent = content.replace(/ /gi, '\n').replace(/<[^>]*>/g, ' ');
+ //console.log("MDLG: Stripped content:", plainTextContent);
+
+ // Combined regex for date & time
+ const dateTimePattern = /([1-2]\d{3}[-/.](0?[1-9]|1[0-2])[-/.](0[1-9]|[12]\d|3[01]))\s+(\d{1,2}:\d{2})/g;
+ const projectPattern = /\[\S+?\]/g;
+ const contextPattern = /(\s|^)(@\S+)/g;
+ const hashtagPattern = /(\s|^)(#\S+)/g;
+ const positivePattern = /(\s|^)(\+\S+)/g;
+ const negativePattern = /(\s|^)(-\S+)/g;
+
+ // Escape HTML characters to prevent issues with replacements
+ function escapeHtml(str) {
+ return str.replace(/[&<>"']/g, function (char) {
+ return {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": '''
+ }[char];
+ });
+ }
+
+ // Helper function to map the number to a colour based on the gradient
+ function getProjectColour(number) {
+ const minColor = { r: 107, g: 109, b: 229 }; // #6b6de5
+ const maxColor = { r: 70, g: 226, b: 96 }; // #46e260
+
+ const ratio = number / 9; // since we are working with 0-9
+
+ // Interpolate the RGB values between minColor and maxColor
+ const r = Math.round(minColor.r + ratio * (maxColor.r - minColor.r));
+ const g = Math.round(minColor.g + ratio * (maxColor.g - minColor.g));
+ const b = Math.round(minColor.b + ratio * (maxColor.b - minColor.b));
+
+ return `rgb(${r}, ${g}, ${b})`;
+ }
+
+ // Process each line of the plain text content
+ let highlightedLines = plainTextContent.split("\n").map(line => {
+ //console.log("MDLG: Processing line:", line);
+
+ // Apply combined date & time highlight
+ line = line.replace(dateTimePattern, `$1$4`);
+
+ // Apply other highlights
+ line = line.replace(projectPattern, (match) => {
+ // Extract the number inside square brackets
+ const number = parseInt(match.match(/\d+/)[0], 10);
+ const colour = getProjectColour(number);
+ return `${escapeHtml(match)}`;
+ });
+
+ // Apply other highlights
+ //line = line.replace(projectPattern, `$&`);
+ line = line.replace(contextPattern, (_, space, tag) => `${space}${escapeHtml(tag)}`);
+ line = line.replace(hashtagPattern, (_, space, tag) => `${space}${escapeHtml(tag)}`);
+ line = line.replace(positivePattern, (_, space, tag) => `${space}${escapeHtml(tag)}`);
+ line = line.replace(negativePattern, (_, space, tag) => `${space}${escapeHtml(tag)}`);
+
+ return line.trim();
+ });
+
+ // Join lines with proper handling
+ let finalContent = highlightedLines.join(" ");
+
+ //console.log("MDLG: Highlighted content:", finalContent);
+ return finalContent;
+}
+
+
+
+
+ async entitiesReloadedEvent({ loadResults }) {
+ //console.log("MDLG: Entities reloaded event triggered");
+ if (loadResults.isNoteContentReloaded(this.noteId)) {
+ //console.log("MDLG: Note content reloaded, refreshing widget...");
+ //this.refresh();
+ } else {
+ //console.log("MDLG: No content reload detected.");
+ }
+ }
+
+
+}
+
+console.log("MDLG: Starting Moodlog Highlighter Widget");
+module.exports = MoodlogHighlighterWidget;
+
+function setNoteContent(noteId, content) {
+ /*const noteElement = document.querySelector(`note-detail-readonly-text-content`);
+
+ if (noteElement) {
+ noteElement.innerHTML = content; // Update the displayed content
+ console.log(`MDLG: Updated note ${noteId} in the DOM.`);
+ } else {
+ console.warn(`MDLG: Note element with data-note-id='${noteId}' not found.`);
+ }*/
+ return api.runOnBackend((id, data) => api.getNote(id).setContent(data), [noteId, content]);
+}
diff --git a/README.md b/README.md
index f623b41..fefccb6 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,58 @@
TriliumScripts
===============
-My collection of scripts for use with trilium
\ No newline at end of file
+Collection of scripts for use with trilium
+
+## syntax highlighting
+
+**highlight_moodlog.js** & **highlight_todo.js** - Both of these will apply appropriate syntaxy highlighting to the specific note.
+
+### installation
+
+1) add the code as the notes contents.
+
+2) set "note type" to "JS frontend"
+
+3) add an "owned attribute" of "#widget"
+
+### use
+
+on the note you want highlighting set the "owned attributes" to "#todotxt #hideHighlightWidget" or "#moodlog #hideHighlightWidget" depending on which syntax type you want to highlight
+
+## bidirectional sync
+
+**trilium_sync.php** - script to keep a folder of local text files and a trilium note in sync.
+
+### installation
+
+Edit the config values at the top of the script to match your trilium setup. Run it manually to test it is working as expected. If it is create a cron job.
+
+### example use
+
+```
+$> php trilium_sync.php
+[i] checking connections
+[i] version: x.xx.x
+[i] gathering info
+
+- Folders -
+Folder/Path Folder Name Parent noteId Mod Trl Mod Dsk Status
+Synced/ Synced root j0DRZhIzV97W 1742502936 1742507367 Exists
+Synced/BothPlaces/ BothPlaces j0DRZhIzV97W AQb3Dg0yZHSu 1742507075 1742507200 Exists
+Synced/[del] tobe del/ [del] tobe del j0DRZhIzV97W j0xUs1FdZL1Y 1742507321 Missing_Disk
+Synced/AddToTrilium/ AddToTrilium 1742507383 Missing_Trilium
+Synced/tobe deleted/ tobe deleted 1742507200 Missing_Trilium
+- Files -
+Folder/Path Filename Parent noteId Mod Trl Mod Dsk Hash Status
+Synced/BothPlaces/ UpdateDiskFile AQb3Dg0yZHSu IwUZLwWdbiWG 1742507314 1742507199 Diff Update_Disk
+Synced/BothPlaces/ UpdateTriliumFile AQb3Dg0yZHSu 7pMPgsb9AVRf 1742507130 1742507345 Diff Update_Trilium
+Synced/[del] tobe del/ gone_also.txt j0xUs1FdZL1Y 4wOoHDy07csr 1742507178 N/A Missing_Disk
+Synced/AddToTrilium/ fromDiskToTrilium.txt 1742507394 N/A Missing_Trilium
+Synced/tobe deleted/ gone_also.txt 1742507200 N/A Missing_Trilium
+
+[-] deleting disk folder: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/tobe deleted/
+[+] creating trilium folder: Synced/AddToTrilium/
+[+] creating trilium note: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/AddToTrilium/fromDiskToTrilium.txt
+[+] updating file on disk: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/BothPlaces/UpdateDiskFile
+[+] updating file on trilium: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/BothPlaces/UpdateTriliumFile
+```
\ No newline at end of file
diff --git a/highlight_moodlog.js b/highlight_moodlog.js
new file mode 100644
index 0000000..be4c283
--- /dev/null
+++ b/highlight_moodlog.js
@@ -0,0 +1,165 @@
+const TPL = `
+ Moodlog Highlighting Applied
+
`;
+
+class MoodlogHighlighterWidget extends api.NoteContextAwareWidget {
+ static get parentWidget() {
+ //console.log("MDLG: Getting parent widget: center-pane");
+ return 'center-pane';
+ }
+
+ get position() {
+ //console.log("MDLG: Setting position: 100");
+ return 100;
+ }
+
+ isEnabled() {
+ const enabled = super.isEnabled()
+ && this.note.type === 'text'
+ && this.note.hasLabel('moodlog');
+ //console.log(`MDLG: isEnabled: ${enabled}`);
+ return enabled;
+ }
+
+ doRender() {
+ //console.log("MDLG: Rendering widget...");
+ this.$widget = $(TPL);
+ //console.log("MDLG: Widget rendered:", this.$widget);
+ return this.$widget;
+ }
+
+ async refreshWithNote(note) {
+ //console.log("MDLG: Refreshing with note:", note);
+ const { content } = await note.getNoteComplement();
+ //console.log("MDLG: Note content fetched:", content);
+
+ // Check if content is fetched properly
+ if (!content) {
+ //console.log("MDLG: No content fetched. Exiting refresh.");
+ return;
+ }
+
+ const highlightedContent = this.applyHighlighting(content);
+ //console.log("MDLG: Highlighted content:", highlightedContent);
+
+ if (highlightedContent !== content) {
+ //console.log("MDLG: Updating note content with highlighted version");
+
+ // Use the setNoteContent function to update the note content
+ try {
+ await setNoteContent(note.noteId, highlightedContent);
+ //console.log("MDLG: Content updated with highlighted version");
+ } catch (error) {
+ //console.error("MDLG: Error updating content:", error);
+ }
+ } else {
+ //console.log("MDLG: No changes to content, skipping update.");
+ }
+ }
+
+
+ applyHighlighting(content) {
+ //console.log("MDLG: Applying highlighting to content...");
+
+ // Replace with \n, then remove all other HTML tags
+ const plainTextContent = content.replace(/ /gi, '\n').replace(/<[^>]*>/g, ' ');
+ //console.log("MDLG: Stripped content:", plainTextContent);
+
+ // Combined regex for date & time
+ const dateTimePattern = /([1-2]\d{3}[-/.](0?[1-9]|1[0-2])[-/.](0[1-9]|[12]\d|3[01]))\s+(\d{1,2}:\d{2})/g;
+ const projectPattern = /\[\S+?\]/g;
+ const contextPattern = /(\s|^)(@\S+)/g;
+ const hashtagPattern = /(\s|^)(#\S+)/g;
+ const positivePattern = /(\s|^)(\+\S+)/g;
+ const negativePattern = /(\s|^)(-\S+)/g;
+
+ // Escape HTML characters to prevent issues with replacements
+ function escapeHtml(str) {
+ return str.replace(/[&<>"']/g, function (char) {
+ return {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": '''
+ }[char];
+ });
+ }
+
+ // Helper function to map the number to a colour based on the gradient
+ function getProjectColour(number) {
+ const minColor = { r: 107, g: 109, b: 229 }; // #6b6de5
+ const maxColor = { r: 70, g: 226, b: 96 }; // #46e260
+
+ const ratio = number / 9; // since we are working with 0-9
+
+ // Interpolate the RGB values between minColor and maxColor
+ const r = Math.round(minColor.r + ratio * (maxColor.r - minColor.r));
+ const g = Math.round(minColor.g + ratio * (maxColor.g - minColor.g));
+ const b = Math.round(minColor.b + ratio * (maxColor.b - minColor.b));
+
+ return `rgb(${r}, ${g}, ${b})`;
+ }
+
+ // Process each line of the plain text content
+ let highlightedLines = plainTextContent.split("\n").map(line => {
+ //console.log("MDLG: Processing line:", line);
+
+ // Apply combined date & time highlight
+ line = line.replace(dateTimePattern, `$1$4`);
+
+ // Apply other highlights
+ line = line.replace(projectPattern, (match) => {
+ // Extract the number inside square brackets
+ const number = parseInt(match.match(/\d+/)[0], 10);
+ const colour = getProjectColour(number);
+ return `${escapeHtml(match)}`;
+ });
+
+ // Apply other highlights
+ //line = line.replace(projectPattern, `$&`);
+ line = line.replace(contextPattern, (_, space, tag) => `${space}${escapeHtml(tag)}`);
+ line = line.replace(hashtagPattern, (_, space, tag) => `${space}${escapeHtml(tag)}`);
+ line = line.replace(positivePattern, (_, space, tag) => `${space}${escapeHtml(tag)}`);
+ line = line.replace(negativePattern, (_, space, tag) => `${space}${escapeHtml(tag)}`);
+
+ return line.trim();
+ });
+
+ // Join lines with proper handling
+ let finalContent = highlightedLines.join(" ");
+
+ //console.log("MDLG: Highlighted content:", finalContent);
+ return finalContent;
+}
+
+
+
+
+ async entitiesReloadedEvent({ loadResults }) {
+ //console.log("MDLG: Entities reloaded event triggered");
+ if (loadResults.isNoteContentReloaded(this.noteId)) {
+ //console.log("MDLG: Note content reloaded, refreshing widget...");
+ //this.refresh();
+ } else {
+ //console.log("MDLG: No content reload detected.");
+ }
+ }
+
+
+}
+
+console.log("MDLG: Starting Moodlog Highlighter Widget");
+module.exports = MoodlogHighlighterWidget;
+
+function setNoteContent(noteId, content) {
+ /*const noteElement = document.querySelector(`note-detail-readonly-text-content`);
+
+ if (noteElement) {
+ noteElement.innerHTML = content; // Update the displayed content
+ console.log(`MDLG: Updated note ${noteId} in the DOM.`);
+ } else {
+ console.warn(`MDLG: Note element with data-note-id='${noteId}' not found.`);
+ }*/
+ return api.runOnBackend((id, data) => api.getNote(id).setContent(data), [noteId, content]);
+}
diff --git a/highlight_todotxt.js b/highlight_todotxt.js
new file mode 100644
index 0000000..1f1b007
--- /dev/null
+++ b/highlight_todotxt.js
@@ -0,0 +1,157 @@
+const TPL = `
+ Todo Highlighting Applied
+
`;
+
+class MoodlogHighlighterWidget extends api.NoteContextAwareWidget {
+ static get parentWidget() {
+ //console.log("MDLG: Getting parent widget: center-pane");
+ return 'center-pane';
+ }
+
+ get position() {
+ //console.log("MDLG: Setting position: 100");
+ return 100;
+ }
+
+ isEnabled() {
+ const enabled = super.isEnabled()
+ && this.note.type === 'text'
+ && this.note.hasLabel('todotxt');
+ //console.log(`MDLG: isEnabled: ${enabled}`);
+ return enabled;
+ }
+
+ doRender() {
+ //console.log("MDLG: Rendering widget...");
+ this.$widget = $(TPL);
+ //console.log("MDLG: Widget rendered:", this.$widget);
+ return this.$widget;
+ }
+
+ async refreshWithNote(note) {
+ //console.log("MDLG: Refreshing with note:", note);
+ const { content } = await note.getNoteComplement();
+ //console.log("MDLG: Note content fetched:", content);
+
+ // Check if content is fetched properly
+ if (!content) {
+ //console.log("MDLG: No content fetched. Exiting refresh.");
+ return;
+ }
+
+ const highlightedContent = this.applyHighlighting(content);
+ //console.log("MDLG: Highlighted content:", highlightedContent);
+
+ if (highlightedContent !== content) {
+ //console.log("MDLG: Updating note content with highlighted version");
+
+ // Use the setNoteContent function to update the note content
+ try {
+ await setNoteContent(note.noteId, highlightedContent);
+ //console.log("MDLG: Content updated with highlighted version");
+ } catch (error) {
+ //console.error("MDLG: Error updating content:", error);
+ }
+ } else {
+ //console.log("MDLG: No changes to content, skipping update.");
+ }
+ }
+
+
+applyHighlighting(content) {
+ // Replace with \n, then remove all other HTML tags
+ const plainTextContent = content.replace(/ /gi, '\n').replace(/<[^>]*>/g, ' ');
+
+ // Regular expressions for Todo.txt syntax
+ const datePattern = /\b(0[1-9]|[12]\d|3[01])\/(0[1-9]|1[0-2])\/([1-2]\d{3})\b/g;
+ const completedTaskPattern = /^\s*x\s+.*/g;
+ const priorityPattern = /^(\s*\([A-E]\))\s+/;
+ const projectPattern = /(?:\s|^)(\+\S+)/g;
+ const contextPattern = /(?:\s|^)(@\S+)/g;
+ const waitPattern = /\bWAIT\b/gi;
+ const customAttributePattern = /(\s+[^\s:]+:[^\s:]+)+\s*$/g;
+
+ function escapeHtml(str) {
+ return str.replace(/[&<>"']/g, char => ({
+ '&': '&', '<': '<', '>': '>',
+ '"': '"', "'": '''
+ })[char]);
+ }
+
+ let highlightedLines = plainTextContent.split("\n").map(line => {
+ // If line starts with 'x', make the whole line grey
+ if (/^\s*x\s+/.test(line)) {
+ return `${line}`;
+ }
+
+ // Highlight priority with different colours based on level
+ line = line.replace(priorityPattern, (match, priority) => {
+ const level = priority.match(/[A-E]/)?.[0];
+ const colors = {
+ 'A': 'rgb(255, 0, 0)', // Red
+ 'B': 'rgb(255, 102, 0)', // Orange
+ 'C': 'rgb(255, 204, 0)', // Yellow
+ 'D': 'rgb(0, 153, 255)', // Blue
+ 'E': 'rgb(102, 204, 102)' // Green
+ };
+ const color = colors[level] || 'black';
+ return `${escapeHtml(priority)} `;
+ });
+
+ // Highlight date
+ line = line.replace(datePattern, (match) => {
+ return `${escapeHtml(match)}`;
+ });
+
+ // Highlight project tags
+ line = line.replace(projectPattern, (_, tag) => ` ${escapeHtml(tag)}`);
+
+ // Highlight context tags
+ line = line.replace(contextPattern, (_, tag) => ` ${escapeHtml(tag)}`);
+
+ // Highlight WAIT command
+ line = line.replace(waitPattern, `WAIT`);
+
+ // Highlight custom attributes
+ line = line.replace(customAttributePattern, `$1`);
+
+ // Remove leading space (if any) from the beginning of the line, without affecting spaces before + or @
+ line = line.replace(/^\s+/, '');
+
+ return line.trim();
+ });
+
+ return highlightedLines.join(" ");
+}
+
+
+
+
+
+ async entitiesReloadedEvent({ loadResults }) {
+ //console.log("MDLG: Entities reloaded event triggered");
+ if (loadResults.isNoteContentReloaded(this.noteId)) {
+ //console.log("MDLG: Note content reloaded, refreshing widget...");
+ //this.refresh();
+ } else {
+ //console.log("MDLG: No content reload detected.");
+ }
+ }
+
+
+}
+
+console.log("TodoHighlight: Starting todotxt Highlighter Widget");
+module.exports = MoodlogHighlighterWidget;
+
+function setNoteContent(noteId, content) {
+ /*const noteElement = document.querySelector(`note-detail-readonly-text-content`);
+
+ if (noteElement) {
+ noteElement.innerHTML = content; // Update the displayed content
+ console.log(`MDLG: Updated note ${noteId} in the DOM.`);
+ } else {
+ console.warn(`MDLG: Note element with data-note-id='${noteId}' not found.`);
+ }*/
+ return api.runOnBackend((id, data) => api.getNote(id).setContent(data), [noteId, content]);
+}
diff --git a/README.md b/README.md
index f623b41..fefccb6 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,58 @@
TriliumScripts
===============
-My collection of scripts for use with trilium
\ No newline at end of file
+Collection of scripts for use with trilium
+
+## syntax highlighting
+
+**highlight_moodlog.js** & **highlight_todo.js** - Both of these will apply appropriate syntaxy highlighting to the specific note.
+
+### installation
+
+1) add the code as the notes contents.
+
+2) set "note type" to "JS frontend"
+
+3) add an "owned attribute" of "#widget"
+
+### use
+
+on the note you want highlighting set the "owned attributes" to "#todotxt #hideHighlightWidget" or "#moodlog #hideHighlightWidget" depending on which syntax type you want to highlight
+
+## bidirectional sync
+
+**trilium_sync.php** - script to keep a folder of local text files and a trilium note in sync.
+
+### installation
+
+Edit the config values at the top of the script to match your trilium setup. Run it manually to test it is working as expected. If it is create a cron job.
+
+### example use
+
+```
+$> php trilium_sync.php
+[i] checking connections
+[i] version: x.xx.x
+[i] gathering info
+
+- Folders -
+Folder/Path Folder Name Parent noteId Mod Trl Mod Dsk Status
+Synced/ Synced root j0DRZhIzV97W 1742502936 1742507367 Exists
+Synced/BothPlaces/ BothPlaces j0DRZhIzV97W AQb3Dg0yZHSu 1742507075 1742507200 Exists
+Synced/[del] tobe del/ [del] tobe del j0DRZhIzV97W j0xUs1FdZL1Y 1742507321 Missing_Disk
+Synced/AddToTrilium/ AddToTrilium 1742507383 Missing_Trilium
+Synced/tobe deleted/ tobe deleted 1742507200 Missing_Trilium
+- Files -
+Folder/Path Filename Parent noteId Mod Trl Mod Dsk Hash Status
+Synced/BothPlaces/ UpdateDiskFile AQb3Dg0yZHSu IwUZLwWdbiWG 1742507314 1742507199 Diff Update_Disk
+Synced/BothPlaces/ UpdateTriliumFile AQb3Dg0yZHSu 7pMPgsb9AVRf 1742507130 1742507345 Diff Update_Trilium
+Synced/[del] tobe del/ gone_also.txt j0xUs1FdZL1Y 4wOoHDy07csr 1742507178 N/A Missing_Disk
+Synced/AddToTrilium/ fromDiskToTrilium.txt 1742507394 N/A Missing_Trilium
+Synced/tobe deleted/ gone_also.txt 1742507200 N/A Missing_Trilium
+
+[-] deleting disk folder: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/tobe deleted/
+[+] creating trilium folder: Synced/AddToTrilium/
+[+] creating trilium note: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/AddToTrilium/fromDiskToTrilium.txt
+[+] updating file on disk: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/BothPlaces/UpdateDiskFile
+[+] updating file on trilium: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/BothPlaces/UpdateTriliumFile
+```
\ No newline at end of file
diff --git a/highlight_moodlog.js b/highlight_moodlog.js
new file mode 100644
index 0000000..be4c283
--- /dev/null
+++ b/highlight_moodlog.js
@@ -0,0 +1,165 @@
+const TPL = `
+ Moodlog Highlighting Applied
+
`;
+
+class MoodlogHighlighterWidget extends api.NoteContextAwareWidget {
+ static get parentWidget() {
+ //console.log("MDLG: Getting parent widget: center-pane");
+ return 'center-pane';
+ }
+
+ get position() {
+ //console.log("MDLG: Setting position: 100");
+ return 100;
+ }
+
+ isEnabled() {
+ const enabled = super.isEnabled()
+ && this.note.type === 'text'
+ && this.note.hasLabel('moodlog');
+ //console.log(`MDLG: isEnabled: ${enabled}`);
+ return enabled;
+ }
+
+ doRender() {
+ //console.log("MDLG: Rendering widget...");
+ this.$widget = $(TPL);
+ //console.log("MDLG: Widget rendered:", this.$widget);
+ return this.$widget;
+ }
+
+ async refreshWithNote(note) {
+ //console.log("MDLG: Refreshing with note:", note);
+ const { content } = await note.getNoteComplement();
+ //console.log("MDLG: Note content fetched:", content);
+
+ // Check if content is fetched properly
+ if (!content) {
+ //console.log("MDLG: No content fetched. Exiting refresh.");
+ return;
+ }
+
+ const highlightedContent = this.applyHighlighting(content);
+ //console.log("MDLG: Highlighted content:", highlightedContent);
+
+ if (highlightedContent !== content) {
+ //console.log("MDLG: Updating note content with highlighted version");
+
+ // Use the setNoteContent function to update the note content
+ try {
+ await setNoteContent(note.noteId, highlightedContent);
+ //console.log("MDLG: Content updated with highlighted version");
+ } catch (error) {
+ //console.error("MDLG: Error updating content:", error);
+ }
+ } else {
+ //console.log("MDLG: No changes to content, skipping update.");
+ }
+ }
+
+
+ applyHighlighting(content) {
+ //console.log("MDLG: Applying highlighting to content...");
+
+ // Replace with \n, then remove all other HTML tags
+ const plainTextContent = content.replace(/ /gi, '\n').replace(/<[^>]*>/g, ' ');
+ //console.log("MDLG: Stripped content:", plainTextContent);
+
+ // Combined regex for date & time
+ const dateTimePattern = /([1-2]\d{3}[-/.](0?[1-9]|1[0-2])[-/.](0[1-9]|[12]\d|3[01]))\s+(\d{1,2}:\d{2})/g;
+ const projectPattern = /\[\S+?\]/g;
+ const contextPattern = /(\s|^)(@\S+)/g;
+ const hashtagPattern = /(\s|^)(#\S+)/g;
+ const positivePattern = /(\s|^)(\+\S+)/g;
+ const negativePattern = /(\s|^)(-\S+)/g;
+
+ // Escape HTML characters to prevent issues with replacements
+ function escapeHtml(str) {
+ return str.replace(/[&<>"']/g, function (char) {
+ return {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": '''
+ }[char];
+ });
+ }
+
+ // Helper function to map the number to a colour based on the gradient
+ function getProjectColour(number) {
+ const minColor = { r: 107, g: 109, b: 229 }; // #6b6de5
+ const maxColor = { r: 70, g: 226, b: 96 }; // #46e260
+
+ const ratio = number / 9; // since we are working with 0-9
+
+ // Interpolate the RGB values between minColor and maxColor
+ const r = Math.round(minColor.r + ratio * (maxColor.r - minColor.r));
+ const g = Math.round(minColor.g + ratio * (maxColor.g - minColor.g));
+ const b = Math.round(minColor.b + ratio * (maxColor.b - minColor.b));
+
+ return `rgb(${r}, ${g}, ${b})`;
+ }
+
+ // Process each line of the plain text content
+ let highlightedLines = plainTextContent.split("\n").map(line => {
+ //console.log("MDLG: Processing line:", line);
+
+ // Apply combined date & time highlight
+ line = line.replace(dateTimePattern, `$1$4`);
+
+ // Apply other highlights
+ line = line.replace(projectPattern, (match) => {
+ // Extract the number inside square brackets
+ const number = parseInt(match.match(/\d+/)[0], 10);
+ const colour = getProjectColour(number);
+ return `${escapeHtml(match)}`;
+ });
+
+ // Apply other highlights
+ //line = line.replace(projectPattern, `$&`);
+ line = line.replace(contextPattern, (_, space, tag) => `${space}${escapeHtml(tag)}`);
+ line = line.replace(hashtagPattern, (_, space, tag) => `${space}${escapeHtml(tag)}`);
+ line = line.replace(positivePattern, (_, space, tag) => `${space}${escapeHtml(tag)}`);
+ line = line.replace(negativePattern, (_, space, tag) => `${space}${escapeHtml(tag)}`);
+
+ return line.trim();
+ });
+
+ // Join lines with proper handling
+ let finalContent = highlightedLines.join(" ");
+
+ //console.log("MDLG: Highlighted content:", finalContent);
+ return finalContent;
+}
+
+
+
+
+ async entitiesReloadedEvent({ loadResults }) {
+ //console.log("MDLG: Entities reloaded event triggered");
+ if (loadResults.isNoteContentReloaded(this.noteId)) {
+ //console.log("MDLG: Note content reloaded, refreshing widget...");
+ //this.refresh();
+ } else {
+ //console.log("MDLG: No content reload detected.");
+ }
+ }
+
+
+}
+
+console.log("MDLG: Starting Moodlog Highlighter Widget");
+module.exports = MoodlogHighlighterWidget;
+
+function setNoteContent(noteId, content) {
+ /*const noteElement = document.querySelector(`note-detail-readonly-text-content`);
+
+ if (noteElement) {
+ noteElement.innerHTML = content; // Update the displayed content
+ console.log(`MDLG: Updated note ${noteId} in the DOM.`);
+ } else {
+ console.warn(`MDLG: Note element with data-note-id='${noteId}' not found.`);
+ }*/
+ return api.runOnBackend((id, data) => api.getNote(id).setContent(data), [noteId, content]);
+}
diff --git a/highlight_todotxt.js b/highlight_todotxt.js
new file mode 100644
index 0000000..1f1b007
--- /dev/null
+++ b/highlight_todotxt.js
@@ -0,0 +1,157 @@
+const TPL = `
+ Todo Highlighting Applied
+
`;
+
+class MoodlogHighlighterWidget extends api.NoteContextAwareWidget {
+ static get parentWidget() {
+ //console.log("MDLG: Getting parent widget: center-pane");
+ return 'center-pane';
+ }
+
+ get position() {
+ //console.log("MDLG: Setting position: 100");
+ return 100;
+ }
+
+ isEnabled() {
+ const enabled = super.isEnabled()
+ && this.note.type === 'text'
+ && this.note.hasLabel('todotxt');
+ //console.log(`MDLG: isEnabled: ${enabled}`);
+ return enabled;
+ }
+
+ doRender() {
+ //console.log("MDLG: Rendering widget...");
+ this.$widget = $(TPL);
+ //console.log("MDLG: Widget rendered:", this.$widget);
+ return this.$widget;
+ }
+
+ async refreshWithNote(note) {
+ //console.log("MDLG: Refreshing with note:", note);
+ const { content } = await note.getNoteComplement();
+ //console.log("MDLG: Note content fetched:", content);
+
+ // Check if content is fetched properly
+ if (!content) {
+ //console.log("MDLG: No content fetched. Exiting refresh.");
+ return;
+ }
+
+ const highlightedContent = this.applyHighlighting(content);
+ //console.log("MDLG: Highlighted content:", highlightedContent);
+
+ if (highlightedContent !== content) {
+ //console.log("MDLG: Updating note content with highlighted version");
+
+ // Use the setNoteContent function to update the note content
+ try {
+ await setNoteContent(note.noteId, highlightedContent);
+ //console.log("MDLG: Content updated with highlighted version");
+ } catch (error) {
+ //console.error("MDLG: Error updating content:", error);
+ }
+ } else {
+ //console.log("MDLG: No changes to content, skipping update.");
+ }
+ }
+
+
+applyHighlighting(content) {
+ // Replace with \n, then remove all other HTML tags
+ const plainTextContent = content.replace(/ /gi, '\n').replace(/<[^>]*>/g, ' ');
+
+ // Regular expressions for Todo.txt syntax
+ const datePattern = /\b(0[1-9]|[12]\d|3[01])\/(0[1-9]|1[0-2])\/([1-2]\d{3})\b/g;
+ const completedTaskPattern = /^\s*x\s+.*/g;
+ const priorityPattern = /^(\s*\([A-E]\))\s+/;
+ const projectPattern = /(?:\s|^)(\+\S+)/g;
+ const contextPattern = /(?:\s|^)(@\S+)/g;
+ const waitPattern = /\bWAIT\b/gi;
+ const customAttributePattern = /(\s+[^\s:]+:[^\s:]+)+\s*$/g;
+
+ function escapeHtml(str) {
+ return str.replace(/[&<>"']/g, char => ({
+ '&': '&', '<': '<', '>': '>',
+ '"': '"', "'": '''
+ })[char]);
+ }
+
+ let highlightedLines = plainTextContent.split("\n").map(line => {
+ // If line starts with 'x', make the whole line grey
+ if (/^\s*x\s+/.test(line)) {
+ return `${line}`;
+ }
+
+ // Highlight priority with different colours based on level
+ line = line.replace(priorityPattern, (match, priority) => {
+ const level = priority.match(/[A-E]/)?.[0];
+ const colors = {
+ 'A': 'rgb(255, 0, 0)', // Red
+ 'B': 'rgb(255, 102, 0)', // Orange
+ 'C': 'rgb(255, 204, 0)', // Yellow
+ 'D': 'rgb(0, 153, 255)', // Blue
+ 'E': 'rgb(102, 204, 102)' // Green
+ };
+ const color = colors[level] || 'black';
+ return `${escapeHtml(priority)} `;
+ });
+
+ // Highlight date
+ line = line.replace(datePattern, (match) => {
+ return `${escapeHtml(match)}`;
+ });
+
+ // Highlight project tags
+ line = line.replace(projectPattern, (_, tag) => ` ${escapeHtml(tag)}`);
+
+ // Highlight context tags
+ line = line.replace(contextPattern, (_, tag) => ` ${escapeHtml(tag)}`);
+
+ // Highlight WAIT command
+ line = line.replace(waitPattern, `WAIT`);
+
+ // Highlight custom attributes
+ line = line.replace(customAttributePattern, `$1`);
+
+ // Remove leading space (if any) from the beginning of the line, without affecting spaces before + or @
+ line = line.replace(/^\s+/, '');
+
+ return line.trim();
+ });
+
+ return highlightedLines.join(" ");
+}
+
+
+
+
+
+ async entitiesReloadedEvent({ loadResults }) {
+ //console.log("MDLG: Entities reloaded event triggered");
+ if (loadResults.isNoteContentReloaded(this.noteId)) {
+ //console.log("MDLG: Note content reloaded, refreshing widget...");
+ //this.refresh();
+ } else {
+ //console.log("MDLG: No content reload detected.");
+ }
+ }
+
+
+}
+
+console.log("TodoHighlight: Starting todotxt Highlighter Widget");
+module.exports = MoodlogHighlighterWidget;
+
+function setNoteContent(noteId, content) {
+ /*const noteElement = document.querySelector(`note-detail-readonly-text-content`);
+
+ if (noteElement) {
+ noteElement.innerHTML = content; // Update the displayed content
+ console.log(`MDLG: Updated note ${noteId} in the DOM.`);
+ } else {
+ console.warn(`MDLG: Note element with data-note-id='${noteId}' not found.`);
+ }*/
+ return api.runOnBackend((id, data) => api.getNote(id).setContent(data), [noteId, content]);
+}
diff --git a/htb2trilium/INSTALL.md b/htb2trilium/INSTALL.md
new file mode 100644
index 0000000..da8afad
--- /dev/null
+++ b/htb2trilium/INSTALL.md
@@ -0,0 +1,41 @@
+## 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:Difficulty=promoted,single,text #label:Solved=promoted,single,text #label:Released=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/README.md b/README.md
index f623b41..fefccb6 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,58 @@
TriliumScripts
===============
-My collection of scripts for use with trilium
\ No newline at end of file
+Collection of scripts for use with trilium
+
+## syntax highlighting
+
+**highlight_moodlog.js** & **highlight_todo.js** - Both of these will apply appropriate syntaxy highlighting to the specific note.
+
+### installation
+
+1) add the code as the notes contents.
+
+2) set "note type" to "JS frontend"
+
+3) add an "owned attribute" of "#widget"
+
+### use
+
+on the note you want highlighting set the "owned attributes" to "#todotxt #hideHighlightWidget" or "#moodlog #hideHighlightWidget" depending on which syntax type you want to highlight
+
+## bidirectional sync
+
+**trilium_sync.php** - script to keep a folder of local text files and a trilium note in sync.
+
+### installation
+
+Edit the config values at the top of the script to match your trilium setup. Run it manually to test it is working as expected. If it is create a cron job.
+
+### example use
+
+```
+$> php trilium_sync.php
+[i] checking connections
+[i] version: x.xx.x
+[i] gathering info
+
+- Folders -
+Folder/Path Folder Name Parent noteId Mod Trl Mod Dsk Status
+Synced/ Synced root j0DRZhIzV97W 1742502936 1742507367 Exists
+Synced/BothPlaces/ BothPlaces j0DRZhIzV97W AQb3Dg0yZHSu 1742507075 1742507200 Exists
+Synced/[del] tobe del/ [del] tobe del j0DRZhIzV97W j0xUs1FdZL1Y 1742507321 Missing_Disk
+Synced/AddToTrilium/ AddToTrilium 1742507383 Missing_Trilium
+Synced/tobe deleted/ tobe deleted 1742507200 Missing_Trilium
+- Files -
+Folder/Path Filename Parent noteId Mod Trl Mod Dsk Hash Status
+Synced/BothPlaces/ UpdateDiskFile AQb3Dg0yZHSu IwUZLwWdbiWG 1742507314 1742507199 Diff Update_Disk
+Synced/BothPlaces/ UpdateTriliumFile AQb3Dg0yZHSu 7pMPgsb9AVRf 1742507130 1742507345 Diff Update_Trilium
+Synced/[del] tobe del/ gone_also.txt j0xUs1FdZL1Y 4wOoHDy07csr 1742507178 N/A Missing_Disk
+Synced/AddToTrilium/ fromDiskToTrilium.txt 1742507394 N/A Missing_Trilium
+Synced/tobe deleted/ gone_also.txt 1742507200 N/A Missing_Trilium
+
+[-] deleting disk folder: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/tobe deleted/
+[+] creating trilium folder: Synced/AddToTrilium/
+[+] creating trilium note: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/AddToTrilium/fromDiskToTrilium.txt
+[+] updating file on disk: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/BothPlaces/UpdateDiskFile
+[+] updating file on trilium: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/BothPlaces/UpdateTriliumFile
+```
\ No newline at end of file
diff --git a/highlight_moodlog.js b/highlight_moodlog.js
new file mode 100644
index 0000000..be4c283
--- /dev/null
+++ b/highlight_moodlog.js
@@ -0,0 +1,165 @@
+const TPL = `
+ Moodlog Highlighting Applied
+
`;
+
+class MoodlogHighlighterWidget extends api.NoteContextAwareWidget {
+ static get parentWidget() {
+ //console.log("MDLG: Getting parent widget: center-pane");
+ return 'center-pane';
+ }
+
+ get position() {
+ //console.log("MDLG: Setting position: 100");
+ return 100;
+ }
+
+ isEnabled() {
+ const enabled = super.isEnabled()
+ && this.note.type === 'text'
+ && this.note.hasLabel('moodlog');
+ //console.log(`MDLG: isEnabled: ${enabled}`);
+ return enabled;
+ }
+
+ doRender() {
+ //console.log("MDLG: Rendering widget...");
+ this.$widget = $(TPL);
+ //console.log("MDLG: Widget rendered:", this.$widget);
+ return this.$widget;
+ }
+
+ async refreshWithNote(note) {
+ //console.log("MDLG: Refreshing with note:", note);
+ const { content } = await note.getNoteComplement();
+ //console.log("MDLG: Note content fetched:", content);
+
+ // Check if content is fetched properly
+ if (!content) {
+ //console.log("MDLG: No content fetched. Exiting refresh.");
+ return;
+ }
+
+ const highlightedContent = this.applyHighlighting(content);
+ //console.log("MDLG: Highlighted content:", highlightedContent);
+
+ if (highlightedContent !== content) {
+ //console.log("MDLG: Updating note content with highlighted version");
+
+ // Use the setNoteContent function to update the note content
+ try {
+ await setNoteContent(note.noteId, highlightedContent);
+ //console.log("MDLG: Content updated with highlighted version");
+ } catch (error) {
+ //console.error("MDLG: Error updating content:", error);
+ }
+ } else {
+ //console.log("MDLG: No changes to content, skipping update.");
+ }
+ }
+
+
+ applyHighlighting(content) {
+ //console.log("MDLG: Applying highlighting to content...");
+
+ // Replace with \n, then remove all other HTML tags
+ const plainTextContent = content.replace(/ /gi, '\n').replace(/<[^>]*>/g, ' ');
+ //console.log("MDLG: Stripped content:", plainTextContent);
+
+ // Combined regex for date & time
+ const dateTimePattern = /([1-2]\d{3}[-/.](0?[1-9]|1[0-2])[-/.](0[1-9]|[12]\d|3[01]))\s+(\d{1,2}:\d{2})/g;
+ const projectPattern = /\[\S+?\]/g;
+ const contextPattern = /(\s|^)(@\S+)/g;
+ const hashtagPattern = /(\s|^)(#\S+)/g;
+ const positivePattern = /(\s|^)(\+\S+)/g;
+ const negativePattern = /(\s|^)(-\S+)/g;
+
+ // Escape HTML characters to prevent issues with replacements
+ function escapeHtml(str) {
+ return str.replace(/[&<>"']/g, function (char) {
+ return {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": '''
+ }[char];
+ });
+ }
+
+ // Helper function to map the number to a colour based on the gradient
+ function getProjectColour(number) {
+ const minColor = { r: 107, g: 109, b: 229 }; // #6b6de5
+ const maxColor = { r: 70, g: 226, b: 96 }; // #46e260
+
+ const ratio = number / 9; // since we are working with 0-9
+
+ // Interpolate the RGB values between minColor and maxColor
+ const r = Math.round(minColor.r + ratio * (maxColor.r - minColor.r));
+ const g = Math.round(minColor.g + ratio * (maxColor.g - minColor.g));
+ const b = Math.round(minColor.b + ratio * (maxColor.b - minColor.b));
+
+ return `rgb(${r}, ${g}, ${b})`;
+ }
+
+ // Process each line of the plain text content
+ let highlightedLines = plainTextContent.split("\n").map(line => {
+ //console.log("MDLG: Processing line:", line);
+
+ // Apply combined date & time highlight
+ line = line.replace(dateTimePattern, `$1$4`);
+
+ // Apply other highlights
+ line = line.replace(projectPattern, (match) => {
+ // Extract the number inside square brackets
+ const number = parseInt(match.match(/\d+/)[0], 10);
+ const colour = getProjectColour(number);
+ return `${escapeHtml(match)}`;
+ });
+
+ // Apply other highlights
+ //line = line.replace(projectPattern, `$&`);
+ line = line.replace(contextPattern, (_, space, tag) => `${space}${escapeHtml(tag)}`);
+ line = line.replace(hashtagPattern, (_, space, tag) => `${space}${escapeHtml(tag)}`);
+ line = line.replace(positivePattern, (_, space, tag) => `${space}${escapeHtml(tag)}`);
+ line = line.replace(negativePattern, (_, space, tag) => `${space}${escapeHtml(tag)}`);
+
+ return line.trim();
+ });
+
+ // Join lines with proper handling
+ let finalContent = highlightedLines.join(" ");
+
+ //console.log("MDLG: Highlighted content:", finalContent);
+ return finalContent;
+}
+
+
+
+
+ async entitiesReloadedEvent({ loadResults }) {
+ //console.log("MDLG: Entities reloaded event triggered");
+ if (loadResults.isNoteContentReloaded(this.noteId)) {
+ //console.log("MDLG: Note content reloaded, refreshing widget...");
+ //this.refresh();
+ } else {
+ //console.log("MDLG: No content reload detected.");
+ }
+ }
+
+
+}
+
+console.log("MDLG: Starting Moodlog Highlighter Widget");
+module.exports = MoodlogHighlighterWidget;
+
+function setNoteContent(noteId, content) {
+ /*const noteElement = document.querySelector(`note-detail-readonly-text-content`);
+
+ if (noteElement) {
+ noteElement.innerHTML = content; // Update the displayed content
+ console.log(`MDLG: Updated note ${noteId} in the DOM.`);
+ } else {
+ console.warn(`MDLG: Note element with data-note-id='${noteId}' not found.`);
+ }*/
+ return api.runOnBackend((id, data) => api.getNote(id).setContent(data), [noteId, content]);
+}
diff --git a/highlight_todotxt.js b/highlight_todotxt.js
new file mode 100644
index 0000000..1f1b007
--- /dev/null
+++ b/highlight_todotxt.js
@@ -0,0 +1,157 @@
+const TPL = `
+ Todo Highlighting Applied
+
`;
+
+class MoodlogHighlighterWidget extends api.NoteContextAwareWidget {
+ static get parentWidget() {
+ //console.log("MDLG: Getting parent widget: center-pane");
+ return 'center-pane';
+ }
+
+ get position() {
+ //console.log("MDLG: Setting position: 100");
+ return 100;
+ }
+
+ isEnabled() {
+ const enabled = super.isEnabled()
+ && this.note.type === 'text'
+ && this.note.hasLabel('todotxt');
+ //console.log(`MDLG: isEnabled: ${enabled}`);
+ return enabled;
+ }
+
+ doRender() {
+ //console.log("MDLG: Rendering widget...");
+ this.$widget = $(TPL);
+ //console.log("MDLG: Widget rendered:", this.$widget);
+ return this.$widget;
+ }
+
+ async refreshWithNote(note) {
+ //console.log("MDLG: Refreshing with note:", note);
+ const { content } = await note.getNoteComplement();
+ //console.log("MDLG: Note content fetched:", content);
+
+ // Check if content is fetched properly
+ if (!content) {
+ //console.log("MDLG: No content fetched. Exiting refresh.");
+ return;
+ }
+
+ const highlightedContent = this.applyHighlighting(content);
+ //console.log("MDLG: Highlighted content:", highlightedContent);
+
+ if (highlightedContent !== content) {
+ //console.log("MDLG: Updating note content with highlighted version");
+
+ // Use the setNoteContent function to update the note content
+ try {
+ await setNoteContent(note.noteId, highlightedContent);
+ //console.log("MDLG: Content updated with highlighted version");
+ } catch (error) {
+ //console.error("MDLG: Error updating content:", error);
+ }
+ } else {
+ //console.log("MDLG: No changes to content, skipping update.");
+ }
+ }
+
+
+applyHighlighting(content) {
+ // Replace with \n, then remove all other HTML tags
+ const plainTextContent = content.replace(/ /gi, '\n').replace(/<[^>]*>/g, ' ');
+
+ // Regular expressions for Todo.txt syntax
+ const datePattern = /\b(0[1-9]|[12]\d|3[01])\/(0[1-9]|1[0-2])\/([1-2]\d{3})\b/g;
+ const completedTaskPattern = /^\s*x\s+.*/g;
+ const priorityPattern = /^(\s*\([A-E]\))\s+/;
+ const projectPattern = /(?:\s|^)(\+\S+)/g;
+ const contextPattern = /(?:\s|^)(@\S+)/g;
+ const waitPattern = /\bWAIT\b/gi;
+ const customAttributePattern = /(\s+[^\s:]+:[^\s:]+)+\s*$/g;
+
+ function escapeHtml(str) {
+ return str.replace(/[&<>"']/g, char => ({
+ '&': '&', '<': '<', '>': '>',
+ '"': '"', "'": '''
+ })[char]);
+ }
+
+ let highlightedLines = plainTextContent.split("\n").map(line => {
+ // If line starts with 'x', make the whole line grey
+ if (/^\s*x\s+/.test(line)) {
+ return `${line}`;
+ }
+
+ // Highlight priority with different colours based on level
+ line = line.replace(priorityPattern, (match, priority) => {
+ const level = priority.match(/[A-E]/)?.[0];
+ const colors = {
+ 'A': 'rgb(255, 0, 0)', // Red
+ 'B': 'rgb(255, 102, 0)', // Orange
+ 'C': 'rgb(255, 204, 0)', // Yellow
+ 'D': 'rgb(0, 153, 255)', // Blue
+ 'E': 'rgb(102, 204, 102)' // Green
+ };
+ const color = colors[level] || 'black';
+ return `${escapeHtml(priority)} `;
+ });
+
+ // Highlight date
+ line = line.replace(datePattern, (match) => {
+ return `${escapeHtml(match)}`;
+ });
+
+ // Highlight project tags
+ line = line.replace(projectPattern, (_, tag) => ` ${escapeHtml(tag)}`);
+
+ // Highlight context tags
+ line = line.replace(contextPattern, (_, tag) => ` ${escapeHtml(tag)}`);
+
+ // Highlight WAIT command
+ line = line.replace(waitPattern, `WAIT`);
+
+ // Highlight custom attributes
+ line = line.replace(customAttributePattern, `$1`);
+
+ // Remove leading space (if any) from the beginning of the line, without affecting spaces before + or @
+ line = line.replace(/^\s+/, '');
+
+ return line.trim();
+ });
+
+ return highlightedLines.join(" ");
+}
+
+
+
+
+
+ async entitiesReloadedEvent({ loadResults }) {
+ //console.log("MDLG: Entities reloaded event triggered");
+ if (loadResults.isNoteContentReloaded(this.noteId)) {
+ //console.log("MDLG: Note content reloaded, refreshing widget...");
+ //this.refresh();
+ } else {
+ //console.log("MDLG: No content reload detected.");
+ }
+ }
+
+
+}
+
+console.log("TodoHighlight: Starting todotxt Highlighter Widget");
+module.exports = MoodlogHighlighterWidget;
+
+function setNoteContent(noteId, content) {
+ /*const noteElement = document.querySelector(`note-detail-readonly-text-content`);
+
+ if (noteElement) {
+ noteElement.innerHTML = content; // Update the displayed content
+ console.log(`MDLG: Updated note ${noteId} in the DOM.`);
+ } else {
+ console.warn(`MDLG: Note element with data-note-id='${noteId}' not found.`);
+ }*/
+ return api.runOnBackend((id, data) => api.getNote(id).setContent(data), [noteId, content]);
+}
diff --git a/htb2trilium/INSTALL.md b/htb2trilium/INSTALL.md
new file mode 100644
index 0000000..da8afad
--- /dev/null
+++ b/htb2trilium/INSTALL.md
@@ -0,0 +1,41 @@
+## 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:Difficulty=promoted,single,text #label:Solved=promoted,single,text #label:Released=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/htb2trilium/README.md b/htb2trilium/README.md
new file mode 100644
index 0000000..1b45ae2
--- /dev/null
+++ b/htb2trilium/README.md
@@ -0,0 +1,16 @@
+htb2trilium
+===============
+
+Pull hackthebox info and stats into personal trilium note taking software
+
+single machine view:
+
+
+
+overview of machines:
+
+
+
+navigation:
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index f623b41..fefccb6 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,58 @@
TriliumScripts
===============
-My collection of scripts for use with trilium
\ No newline at end of file
+Collection of scripts for use with trilium
+
+## syntax highlighting
+
+**highlight_moodlog.js** & **highlight_todo.js** - Both of these will apply appropriate syntaxy highlighting to the specific note.
+
+### installation
+
+1) add the code as the notes contents.
+
+2) set "note type" to "JS frontend"
+
+3) add an "owned attribute" of "#widget"
+
+### use
+
+on the note you want highlighting set the "owned attributes" to "#todotxt #hideHighlightWidget" or "#moodlog #hideHighlightWidget" depending on which syntax type you want to highlight
+
+## bidirectional sync
+
+**trilium_sync.php** - script to keep a folder of local text files and a trilium note in sync.
+
+### installation
+
+Edit the config values at the top of the script to match your trilium setup. Run it manually to test it is working as expected. If it is create a cron job.
+
+### example use
+
+```
+$> php trilium_sync.php
+[i] checking connections
+[i] version: x.xx.x
+[i] gathering info
+
+- Folders -
+Folder/Path Folder Name Parent noteId Mod Trl Mod Dsk Status
+Synced/ Synced root j0DRZhIzV97W 1742502936 1742507367 Exists
+Synced/BothPlaces/ BothPlaces j0DRZhIzV97W AQb3Dg0yZHSu 1742507075 1742507200 Exists
+Synced/[del] tobe del/ [del] tobe del j0DRZhIzV97W j0xUs1FdZL1Y 1742507321 Missing_Disk
+Synced/AddToTrilium/ AddToTrilium 1742507383 Missing_Trilium
+Synced/tobe deleted/ tobe deleted 1742507200 Missing_Trilium
+- Files -
+Folder/Path Filename Parent noteId Mod Trl Mod Dsk Hash Status
+Synced/BothPlaces/ UpdateDiskFile AQb3Dg0yZHSu IwUZLwWdbiWG 1742507314 1742507199 Diff Update_Disk
+Synced/BothPlaces/ UpdateTriliumFile AQb3Dg0yZHSu 7pMPgsb9AVRf 1742507130 1742507345 Diff Update_Trilium
+Synced/[del] tobe del/ gone_also.txt j0xUs1FdZL1Y 4wOoHDy07csr 1742507178 N/A Missing_Disk
+Synced/AddToTrilium/ fromDiskToTrilium.txt 1742507394 N/A Missing_Trilium
+Synced/tobe deleted/ gone_also.txt 1742507200 N/A Missing_Trilium
+
+[-] deleting disk folder: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/tobe deleted/
+[+] creating trilium folder: Synced/AddToTrilium/
+[+] creating trilium note: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/AddToTrilium/fromDiskToTrilium.txt
+[+] updating file on disk: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/BothPlaces/UpdateDiskFile
+[+] updating file on trilium: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/BothPlaces/UpdateTriliumFile
+```
\ No newline at end of file
diff --git a/highlight_moodlog.js b/highlight_moodlog.js
new file mode 100644
index 0000000..be4c283
--- /dev/null
+++ b/highlight_moodlog.js
@@ -0,0 +1,165 @@
+const TPL = `
+ Moodlog Highlighting Applied
+
`;
+
+class MoodlogHighlighterWidget extends api.NoteContextAwareWidget {
+ static get parentWidget() {
+ //console.log("MDLG: Getting parent widget: center-pane");
+ return 'center-pane';
+ }
+
+ get position() {
+ //console.log("MDLG: Setting position: 100");
+ return 100;
+ }
+
+ isEnabled() {
+ const enabled = super.isEnabled()
+ && this.note.type === 'text'
+ && this.note.hasLabel('moodlog');
+ //console.log(`MDLG: isEnabled: ${enabled}`);
+ return enabled;
+ }
+
+ doRender() {
+ //console.log("MDLG: Rendering widget...");
+ this.$widget = $(TPL);
+ //console.log("MDLG: Widget rendered:", this.$widget);
+ return this.$widget;
+ }
+
+ async refreshWithNote(note) {
+ //console.log("MDLG: Refreshing with note:", note);
+ const { content } = await note.getNoteComplement();
+ //console.log("MDLG: Note content fetched:", content);
+
+ // Check if content is fetched properly
+ if (!content) {
+ //console.log("MDLG: No content fetched. Exiting refresh.");
+ return;
+ }
+
+ const highlightedContent = this.applyHighlighting(content);
+ //console.log("MDLG: Highlighted content:", highlightedContent);
+
+ if (highlightedContent !== content) {
+ //console.log("MDLG: Updating note content with highlighted version");
+
+ // Use the setNoteContent function to update the note content
+ try {
+ await setNoteContent(note.noteId, highlightedContent);
+ //console.log("MDLG: Content updated with highlighted version");
+ } catch (error) {
+ //console.error("MDLG: Error updating content:", error);
+ }
+ } else {
+ //console.log("MDLG: No changes to content, skipping update.");
+ }
+ }
+
+
+ applyHighlighting(content) {
+ //console.log("MDLG: Applying highlighting to content...");
+
+ // Replace with \n, then remove all other HTML tags
+ const plainTextContent = content.replace(/ /gi, '\n').replace(/<[^>]*>/g, ' ');
+ //console.log("MDLG: Stripped content:", plainTextContent);
+
+ // Combined regex for date & time
+ const dateTimePattern = /([1-2]\d{3}[-/.](0?[1-9]|1[0-2])[-/.](0[1-9]|[12]\d|3[01]))\s+(\d{1,2}:\d{2})/g;
+ const projectPattern = /\[\S+?\]/g;
+ const contextPattern = /(\s|^)(@\S+)/g;
+ const hashtagPattern = /(\s|^)(#\S+)/g;
+ const positivePattern = /(\s|^)(\+\S+)/g;
+ const negativePattern = /(\s|^)(-\S+)/g;
+
+ // Escape HTML characters to prevent issues with replacements
+ function escapeHtml(str) {
+ return str.replace(/[&<>"']/g, function (char) {
+ return {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": '''
+ }[char];
+ });
+ }
+
+ // Helper function to map the number to a colour based on the gradient
+ function getProjectColour(number) {
+ const minColor = { r: 107, g: 109, b: 229 }; // #6b6de5
+ const maxColor = { r: 70, g: 226, b: 96 }; // #46e260
+
+ const ratio = number / 9; // since we are working with 0-9
+
+ // Interpolate the RGB values between minColor and maxColor
+ const r = Math.round(minColor.r + ratio * (maxColor.r - minColor.r));
+ const g = Math.round(minColor.g + ratio * (maxColor.g - minColor.g));
+ const b = Math.round(minColor.b + ratio * (maxColor.b - minColor.b));
+
+ return `rgb(${r}, ${g}, ${b})`;
+ }
+
+ // Process each line of the plain text content
+ let highlightedLines = plainTextContent.split("\n").map(line => {
+ //console.log("MDLG: Processing line:", line);
+
+ // Apply combined date & time highlight
+ line = line.replace(dateTimePattern, `$1$4`);
+
+ // Apply other highlights
+ line = line.replace(projectPattern, (match) => {
+ // Extract the number inside square brackets
+ const number = parseInt(match.match(/\d+/)[0], 10);
+ const colour = getProjectColour(number);
+ return `${escapeHtml(match)}`;
+ });
+
+ // Apply other highlights
+ //line = line.replace(projectPattern, `$&`);
+ line = line.replace(contextPattern, (_, space, tag) => `${space}${escapeHtml(tag)}`);
+ line = line.replace(hashtagPattern, (_, space, tag) => `${space}${escapeHtml(tag)}`);
+ line = line.replace(positivePattern, (_, space, tag) => `${space}${escapeHtml(tag)}`);
+ line = line.replace(negativePattern, (_, space, tag) => `${space}${escapeHtml(tag)}`);
+
+ return line.trim();
+ });
+
+ // Join lines with proper handling
+ let finalContent = highlightedLines.join(" ");
+
+ //console.log("MDLG: Highlighted content:", finalContent);
+ return finalContent;
+}
+
+
+
+
+ async entitiesReloadedEvent({ loadResults }) {
+ //console.log("MDLG: Entities reloaded event triggered");
+ if (loadResults.isNoteContentReloaded(this.noteId)) {
+ //console.log("MDLG: Note content reloaded, refreshing widget...");
+ //this.refresh();
+ } else {
+ //console.log("MDLG: No content reload detected.");
+ }
+ }
+
+
+}
+
+console.log("MDLG: Starting Moodlog Highlighter Widget");
+module.exports = MoodlogHighlighterWidget;
+
+function setNoteContent(noteId, content) {
+ /*const noteElement = document.querySelector(`note-detail-readonly-text-content`);
+
+ if (noteElement) {
+ noteElement.innerHTML = content; // Update the displayed content
+ console.log(`MDLG: Updated note ${noteId} in the DOM.`);
+ } else {
+ console.warn(`MDLG: Note element with data-note-id='${noteId}' not found.`);
+ }*/
+ return api.runOnBackend((id, data) => api.getNote(id).setContent(data), [noteId, content]);
+}
diff --git a/highlight_todotxt.js b/highlight_todotxt.js
new file mode 100644
index 0000000..1f1b007
--- /dev/null
+++ b/highlight_todotxt.js
@@ -0,0 +1,157 @@
+const TPL = `
+ Todo Highlighting Applied
+
`;
+
+class MoodlogHighlighterWidget extends api.NoteContextAwareWidget {
+ static get parentWidget() {
+ //console.log("MDLG: Getting parent widget: center-pane");
+ return 'center-pane';
+ }
+
+ get position() {
+ //console.log("MDLG: Setting position: 100");
+ return 100;
+ }
+
+ isEnabled() {
+ const enabled = super.isEnabled()
+ && this.note.type === 'text'
+ && this.note.hasLabel('todotxt');
+ //console.log(`MDLG: isEnabled: ${enabled}`);
+ return enabled;
+ }
+
+ doRender() {
+ //console.log("MDLG: Rendering widget...");
+ this.$widget = $(TPL);
+ //console.log("MDLG: Widget rendered:", this.$widget);
+ return this.$widget;
+ }
+
+ async refreshWithNote(note) {
+ //console.log("MDLG: Refreshing with note:", note);
+ const { content } = await note.getNoteComplement();
+ //console.log("MDLG: Note content fetched:", content);
+
+ // Check if content is fetched properly
+ if (!content) {
+ //console.log("MDLG: No content fetched. Exiting refresh.");
+ return;
+ }
+
+ const highlightedContent = this.applyHighlighting(content);
+ //console.log("MDLG: Highlighted content:", highlightedContent);
+
+ if (highlightedContent !== content) {
+ //console.log("MDLG: Updating note content with highlighted version");
+
+ // Use the setNoteContent function to update the note content
+ try {
+ await setNoteContent(note.noteId, highlightedContent);
+ //console.log("MDLG: Content updated with highlighted version");
+ } catch (error) {
+ //console.error("MDLG: Error updating content:", error);
+ }
+ } else {
+ //console.log("MDLG: No changes to content, skipping update.");
+ }
+ }
+
+
+applyHighlighting(content) {
+ // Replace with \n, then remove all other HTML tags
+ const plainTextContent = content.replace(/ /gi, '\n').replace(/<[^>]*>/g, ' ');
+
+ // Regular expressions for Todo.txt syntax
+ const datePattern = /\b(0[1-9]|[12]\d|3[01])\/(0[1-9]|1[0-2])\/([1-2]\d{3})\b/g;
+ const completedTaskPattern = /^\s*x\s+.*/g;
+ const priorityPattern = /^(\s*\([A-E]\))\s+/;
+ const projectPattern = /(?:\s|^)(\+\S+)/g;
+ const contextPattern = /(?:\s|^)(@\S+)/g;
+ const waitPattern = /\bWAIT\b/gi;
+ const customAttributePattern = /(\s+[^\s:]+:[^\s:]+)+\s*$/g;
+
+ function escapeHtml(str) {
+ return str.replace(/[&<>"']/g, char => ({
+ '&': '&', '<': '<', '>': '>',
+ '"': '"', "'": '''
+ })[char]);
+ }
+
+ let highlightedLines = plainTextContent.split("\n").map(line => {
+ // If line starts with 'x', make the whole line grey
+ if (/^\s*x\s+/.test(line)) {
+ return `${line}`;
+ }
+
+ // Highlight priority with different colours based on level
+ line = line.replace(priorityPattern, (match, priority) => {
+ const level = priority.match(/[A-E]/)?.[0];
+ const colors = {
+ 'A': 'rgb(255, 0, 0)', // Red
+ 'B': 'rgb(255, 102, 0)', // Orange
+ 'C': 'rgb(255, 204, 0)', // Yellow
+ 'D': 'rgb(0, 153, 255)', // Blue
+ 'E': 'rgb(102, 204, 102)' // Green
+ };
+ const color = colors[level] || 'black';
+ return `${escapeHtml(priority)} `;
+ });
+
+ // Highlight date
+ line = line.replace(datePattern, (match) => {
+ return `${escapeHtml(match)}`;
+ });
+
+ // Highlight project tags
+ line = line.replace(projectPattern, (_, tag) => ` ${escapeHtml(tag)}`);
+
+ // Highlight context tags
+ line = line.replace(contextPattern, (_, tag) => ` ${escapeHtml(tag)}`);
+
+ // Highlight WAIT command
+ line = line.replace(waitPattern, `WAIT`);
+
+ // Highlight custom attributes
+ line = line.replace(customAttributePattern, `$1`);
+
+ // Remove leading space (if any) from the beginning of the line, without affecting spaces before + or @
+ line = line.replace(/^\s+/, '');
+
+ return line.trim();
+ });
+
+ return highlightedLines.join(" ");
+}
+
+
+
+
+
+ async entitiesReloadedEvent({ loadResults }) {
+ //console.log("MDLG: Entities reloaded event triggered");
+ if (loadResults.isNoteContentReloaded(this.noteId)) {
+ //console.log("MDLG: Note content reloaded, refreshing widget...");
+ //this.refresh();
+ } else {
+ //console.log("MDLG: No content reload detected.");
+ }
+ }
+
+
+}
+
+console.log("TodoHighlight: Starting todotxt Highlighter Widget");
+module.exports = MoodlogHighlighterWidget;
+
+function setNoteContent(noteId, content) {
+ /*const noteElement = document.querySelector(`note-detail-readonly-text-content`);
+
+ if (noteElement) {
+ noteElement.innerHTML = content; // Update the displayed content
+ console.log(`MDLG: Updated note ${noteId} in the DOM.`);
+ } else {
+ console.warn(`MDLG: Note element with data-note-id='${noteId}' not found.`);
+ }*/
+ return api.runOnBackend((id, data) => api.getNote(id).setContent(data), [noteId, content]);
+}
diff --git a/htb2trilium/INSTALL.md b/htb2trilium/INSTALL.md
new file mode 100644
index 0000000..da8afad
--- /dev/null
+++ b/htb2trilium/INSTALL.md
@@ -0,0 +1,41 @@
+## 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:Difficulty=promoted,single,text #label:Solved=promoted,single,text #label:Released=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/htb2trilium/README.md b/htb2trilium/README.md
new file mode 100644
index 0000000..1b45ae2
--- /dev/null
+++ b/htb2trilium/README.md
@@ -0,0 +1,16 @@
+htb2trilium
+===============
+
+Pull hackthebox info and stats into personal trilium note taking software
+
+single machine view:
+
+
+
+overview of machines:
+
+
+
+navigation:
+
+
\ No newline at end of file
diff --git a/htb2trilium/config.json b/htb2trilium/config.json
new file mode 100644
index 0000000..39597ce
--- /dev/null
+++ b/htb2trilium/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/README.md b/README.md
index f623b41..fefccb6 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,58 @@
TriliumScripts
===============
-My collection of scripts for use with trilium
\ No newline at end of file
+Collection of scripts for use with trilium
+
+## syntax highlighting
+
+**highlight_moodlog.js** & **highlight_todo.js** - Both of these will apply appropriate syntaxy highlighting to the specific note.
+
+### installation
+
+1) add the code as the notes contents.
+
+2) set "note type" to "JS frontend"
+
+3) add an "owned attribute" of "#widget"
+
+### use
+
+on the note you want highlighting set the "owned attributes" to "#todotxt #hideHighlightWidget" or "#moodlog #hideHighlightWidget" depending on which syntax type you want to highlight
+
+## bidirectional sync
+
+**trilium_sync.php** - script to keep a folder of local text files and a trilium note in sync.
+
+### installation
+
+Edit the config values at the top of the script to match your trilium setup. Run it manually to test it is working as expected. If it is create a cron job.
+
+### example use
+
+```
+$> php trilium_sync.php
+[i] checking connections
+[i] version: x.xx.x
+[i] gathering info
+
+- Folders -
+Folder/Path Folder Name Parent noteId Mod Trl Mod Dsk Status
+Synced/ Synced root j0DRZhIzV97W 1742502936 1742507367 Exists
+Synced/BothPlaces/ BothPlaces j0DRZhIzV97W AQb3Dg0yZHSu 1742507075 1742507200 Exists
+Synced/[del] tobe del/ [del] tobe del j0DRZhIzV97W j0xUs1FdZL1Y 1742507321 Missing_Disk
+Synced/AddToTrilium/ AddToTrilium 1742507383 Missing_Trilium
+Synced/tobe deleted/ tobe deleted 1742507200 Missing_Trilium
+- Files -
+Folder/Path Filename Parent noteId Mod Trl Mod Dsk Hash Status
+Synced/BothPlaces/ UpdateDiskFile AQb3Dg0yZHSu IwUZLwWdbiWG 1742507314 1742507199 Diff Update_Disk
+Synced/BothPlaces/ UpdateTriliumFile AQb3Dg0yZHSu 7pMPgsb9AVRf 1742507130 1742507345 Diff Update_Trilium
+Synced/[del] tobe del/ gone_also.txt j0xUs1FdZL1Y 4wOoHDy07csr 1742507178 N/A Missing_Disk
+Synced/AddToTrilium/ fromDiskToTrilium.txt 1742507394 N/A Missing_Trilium
+Synced/tobe deleted/ gone_also.txt 1742507200 N/A Missing_Trilium
+
+[-] deleting disk folder: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/tobe deleted/
+[+] creating trilium folder: Synced/AddToTrilium/
+[+] creating trilium note: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/AddToTrilium/fromDiskToTrilium.txt
+[+] updating file on disk: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/BothPlaces/UpdateDiskFile
+[+] updating file on trilium: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/BothPlaces/UpdateTriliumFile
+```
\ No newline at end of file
diff --git a/highlight_moodlog.js b/highlight_moodlog.js
new file mode 100644
index 0000000..be4c283
--- /dev/null
+++ b/highlight_moodlog.js
@@ -0,0 +1,165 @@
+const TPL = `
+ Moodlog Highlighting Applied
+
`;
+
+class MoodlogHighlighterWidget extends api.NoteContextAwareWidget {
+ static get parentWidget() {
+ //console.log("MDLG: Getting parent widget: center-pane");
+ return 'center-pane';
+ }
+
+ get position() {
+ //console.log("MDLG: Setting position: 100");
+ return 100;
+ }
+
+ isEnabled() {
+ const enabled = super.isEnabled()
+ && this.note.type === 'text'
+ && this.note.hasLabel('moodlog');
+ //console.log(`MDLG: isEnabled: ${enabled}`);
+ return enabled;
+ }
+
+ doRender() {
+ //console.log("MDLG: Rendering widget...");
+ this.$widget = $(TPL);
+ //console.log("MDLG: Widget rendered:", this.$widget);
+ return this.$widget;
+ }
+
+ async refreshWithNote(note) {
+ //console.log("MDLG: Refreshing with note:", note);
+ const { content } = await note.getNoteComplement();
+ //console.log("MDLG: Note content fetched:", content);
+
+ // Check if content is fetched properly
+ if (!content) {
+ //console.log("MDLG: No content fetched. Exiting refresh.");
+ return;
+ }
+
+ const highlightedContent = this.applyHighlighting(content);
+ //console.log("MDLG: Highlighted content:", highlightedContent);
+
+ if (highlightedContent !== content) {
+ //console.log("MDLG: Updating note content with highlighted version");
+
+ // Use the setNoteContent function to update the note content
+ try {
+ await setNoteContent(note.noteId, highlightedContent);
+ //console.log("MDLG: Content updated with highlighted version");
+ } catch (error) {
+ //console.error("MDLG: Error updating content:", error);
+ }
+ } else {
+ //console.log("MDLG: No changes to content, skipping update.");
+ }
+ }
+
+
+ applyHighlighting(content) {
+ //console.log("MDLG: Applying highlighting to content...");
+
+ // Replace with \n, then remove all other HTML tags
+ const plainTextContent = content.replace(/ /gi, '\n').replace(/<[^>]*>/g, ' ');
+ //console.log("MDLG: Stripped content:", plainTextContent);
+
+ // Combined regex for date & time
+ const dateTimePattern = /([1-2]\d{3}[-/.](0?[1-9]|1[0-2])[-/.](0[1-9]|[12]\d|3[01]))\s+(\d{1,2}:\d{2})/g;
+ const projectPattern = /\[\S+?\]/g;
+ const contextPattern = /(\s|^)(@\S+)/g;
+ const hashtagPattern = /(\s|^)(#\S+)/g;
+ const positivePattern = /(\s|^)(\+\S+)/g;
+ const negativePattern = /(\s|^)(-\S+)/g;
+
+ // Escape HTML characters to prevent issues with replacements
+ function escapeHtml(str) {
+ return str.replace(/[&<>"']/g, function (char) {
+ return {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": '''
+ }[char];
+ });
+ }
+
+ // Helper function to map the number to a colour based on the gradient
+ function getProjectColour(number) {
+ const minColor = { r: 107, g: 109, b: 229 }; // #6b6de5
+ const maxColor = { r: 70, g: 226, b: 96 }; // #46e260
+
+ const ratio = number / 9; // since we are working with 0-9
+
+ // Interpolate the RGB values between minColor and maxColor
+ const r = Math.round(minColor.r + ratio * (maxColor.r - minColor.r));
+ const g = Math.round(minColor.g + ratio * (maxColor.g - minColor.g));
+ const b = Math.round(minColor.b + ratio * (maxColor.b - minColor.b));
+
+ return `rgb(${r}, ${g}, ${b})`;
+ }
+
+ // Process each line of the plain text content
+ let highlightedLines = plainTextContent.split("\n").map(line => {
+ //console.log("MDLG: Processing line:", line);
+
+ // Apply combined date & time highlight
+ line = line.replace(dateTimePattern, `$1$4`);
+
+ // Apply other highlights
+ line = line.replace(projectPattern, (match) => {
+ // Extract the number inside square brackets
+ const number = parseInt(match.match(/\d+/)[0], 10);
+ const colour = getProjectColour(number);
+ return `${escapeHtml(match)}`;
+ });
+
+ // Apply other highlights
+ //line = line.replace(projectPattern, `$&`);
+ line = line.replace(contextPattern, (_, space, tag) => `${space}${escapeHtml(tag)}`);
+ line = line.replace(hashtagPattern, (_, space, tag) => `${space}${escapeHtml(tag)}`);
+ line = line.replace(positivePattern, (_, space, tag) => `${space}${escapeHtml(tag)}`);
+ line = line.replace(negativePattern, (_, space, tag) => `${space}${escapeHtml(tag)}`);
+
+ return line.trim();
+ });
+
+ // Join lines with proper handling
+ let finalContent = highlightedLines.join(" ");
+
+ //console.log("MDLG: Highlighted content:", finalContent);
+ return finalContent;
+}
+
+
+
+
+ async entitiesReloadedEvent({ loadResults }) {
+ //console.log("MDLG: Entities reloaded event triggered");
+ if (loadResults.isNoteContentReloaded(this.noteId)) {
+ //console.log("MDLG: Note content reloaded, refreshing widget...");
+ //this.refresh();
+ } else {
+ //console.log("MDLG: No content reload detected.");
+ }
+ }
+
+
+}
+
+console.log("MDLG: Starting Moodlog Highlighter Widget");
+module.exports = MoodlogHighlighterWidget;
+
+function setNoteContent(noteId, content) {
+ /*const noteElement = document.querySelector(`note-detail-readonly-text-content`);
+
+ if (noteElement) {
+ noteElement.innerHTML = content; // Update the displayed content
+ console.log(`MDLG: Updated note ${noteId} in the DOM.`);
+ } else {
+ console.warn(`MDLG: Note element with data-note-id='${noteId}' not found.`);
+ }*/
+ return api.runOnBackend((id, data) => api.getNote(id).setContent(data), [noteId, content]);
+}
diff --git a/highlight_todotxt.js b/highlight_todotxt.js
new file mode 100644
index 0000000..1f1b007
--- /dev/null
+++ b/highlight_todotxt.js
@@ -0,0 +1,157 @@
+const TPL = `
+ Todo Highlighting Applied
+
`;
+
+class MoodlogHighlighterWidget extends api.NoteContextAwareWidget {
+ static get parentWidget() {
+ //console.log("MDLG: Getting parent widget: center-pane");
+ return 'center-pane';
+ }
+
+ get position() {
+ //console.log("MDLG: Setting position: 100");
+ return 100;
+ }
+
+ isEnabled() {
+ const enabled = super.isEnabled()
+ && this.note.type === 'text'
+ && this.note.hasLabel('todotxt');
+ //console.log(`MDLG: isEnabled: ${enabled}`);
+ return enabled;
+ }
+
+ doRender() {
+ //console.log("MDLG: Rendering widget...");
+ this.$widget = $(TPL);
+ //console.log("MDLG: Widget rendered:", this.$widget);
+ return this.$widget;
+ }
+
+ async refreshWithNote(note) {
+ //console.log("MDLG: Refreshing with note:", note);
+ const { content } = await note.getNoteComplement();
+ //console.log("MDLG: Note content fetched:", content);
+
+ // Check if content is fetched properly
+ if (!content) {
+ //console.log("MDLG: No content fetched. Exiting refresh.");
+ return;
+ }
+
+ const highlightedContent = this.applyHighlighting(content);
+ //console.log("MDLG: Highlighted content:", highlightedContent);
+
+ if (highlightedContent !== content) {
+ //console.log("MDLG: Updating note content with highlighted version");
+
+ // Use the setNoteContent function to update the note content
+ try {
+ await setNoteContent(note.noteId, highlightedContent);
+ //console.log("MDLG: Content updated with highlighted version");
+ } catch (error) {
+ //console.error("MDLG: Error updating content:", error);
+ }
+ } else {
+ //console.log("MDLG: No changes to content, skipping update.");
+ }
+ }
+
+
+applyHighlighting(content) {
+ // Replace with \n, then remove all other HTML tags
+ const plainTextContent = content.replace(/ /gi, '\n').replace(/<[^>]*>/g, ' ');
+
+ // Regular expressions for Todo.txt syntax
+ const datePattern = /\b(0[1-9]|[12]\d|3[01])\/(0[1-9]|1[0-2])\/([1-2]\d{3})\b/g;
+ const completedTaskPattern = /^\s*x\s+.*/g;
+ const priorityPattern = /^(\s*\([A-E]\))\s+/;
+ const projectPattern = /(?:\s|^)(\+\S+)/g;
+ const contextPattern = /(?:\s|^)(@\S+)/g;
+ const waitPattern = /\bWAIT\b/gi;
+ const customAttributePattern = /(\s+[^\s:]+:[^\s:]+)+\s*$/g;
+
+ function escapeHtml(str) {
+ return str.replace(/[&<>"']/g, char => ({
+ '&': '&', '<': '<', '>': '>',
+ '"': '"', "'": '''
+ })[char]);
+ }
+
+ let highlightedLines = plainTextContent.split("\n").map(line => {
+ // If line starts with 'x', make the whole line grey
+ if (/^\s*x\s+/.test(line)) {
+ return `${line}`;
+ }
+
+ // Highlight priority with different colours based on level
+ line = line.replace(priorityPattern, (match, priority) => {
+ const level = priority.match(/[A-E]/)?.[0];
+ const colors = {
+ 'A': 'rgb(255, 0, 0)', // Red
+ 'B': 'rgb(255, 102, 0)', // Orange
+ 'C': 'rgb(255, 204, 0)', // Yellow
+ 'D': 'rgb(0, 153, 255)', // Blue
+ 'E': 'rgb(102, 204, 102)' // Green
+ };
+ const color = colors[level] || 'black';
+ return `${escapeHtml(priority)} `;
+ });
+
+ // Highlight date
+ line = line.replace(datePattern, (match) => {
+ return `${escapeHtml(match)}`;
+ });
+
+ // Highlight project tags
+ line = line.replace(projectPattern, (_, tag) => ` ${escapeHtml(tag)}`);
+
+ // Highlight context tags
+ line = line.replace(contextPattern, (_, tag) => ` ${escapeHtml(tag)}`);
+
+ // Highlight WAIT command
+ line = line.replace(waitPattern, `WAIT`);
+
+ // Highlight custom attributes
+ line = line.replace(customAttributePattern, `$1`);
+
+ // Remove leading space (if any) from the beginning of the line, without affecting spaces before + or @
+ line = line.replace(/^\s+/, '');
+
+ return line.trim();
+ });
+
+ return highlightedLines.join(" ");
+}
+
+
+
+
+
+ async entitiesReloadedEvent({ loadResults }) {
+ //console.log("MDLG: Entities reloaded event triggered");
+ if (loadResults.isNoteContentReloaded(this.noteId)) {
+ //console.log("MDLG: Note content reloaded, refreshing widget...");
+ //this.refresh();
+ } else {
+ //console.log("MDLG: No content reload detected.");
+ }
+ }
+
+
+}
+
+console.log("TodoHighlight: Starting todotxt Highlighter Widget");
+module.exports = MoodlogHighlighterWidget;
+
+function setNoteContent(noteId, content) {
+ /*const noteElement = document.querySelector(`note-detail-readonly-text-content`);
+
+ if (noteElement) {
+ noteElement.innerHTML = content; // Update the displayed content
+ console.log(`MDLG: Updated note ${noteId} in the DOM.`);
+ } else {
+ console.warn(`MDLG: Note element with data-note-id='${noteId}' not found.`);
+ }*/
+ return api.runOnBackend((id, data) => api.getNote(id).setContent(data), [noteId, content]);
+}
diff --git a/htb2trilium/INSTALL.md b/htb2trilium/INSTALL.md
new file mode 100644
index 0000000..da8afad
--- /dev/null
+++ b/htb2trilium/INSTALL.md
@@ -0,0 +1,41 @@
+## 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:Difficulty=promoted,single,text #label:Solved=promoted,single,text #label:Released=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/htb2trilium/README.md b/htb2trilium/README.md
new file mode 100644
index 0000000..1b45ae2
--- /dev/null
+++ b/htb2trilium/README.md
@@ -0,0 +1,16 @@
+htb2trilium
+===============
+
+Pull hackthebox info and stats into personal trilium note taking software
+
+single machine view:
+
+
+
+overview of machines:
+
+
+
+navigation:
+
+
\ No newline at end of file
diff --git a/htb2trilium/config.json b/htb2trilium/config.json
new file mode 100644
index 0000000..39597ce
--- /dev/null
+++ b/htb2trilium/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/htb2trilium_challenges.py b/htb2trilium/htb2trilium_challenges.py
new file mode 100644
index 0000000..d0d95ca
--- /dev/null
+++ b/htb2trilium/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'].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)))
diff --git a/README.md b/README.md
index f623b41..fefccb6 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,58 @@
TriliumScripts
===============
-My collection of scripts for use with trilium
\ No newline at end of file
+Collection of scripts for use with trilium
+
+## syntax highlighting
+
+**highlight_moodlog.js** & **highlight_todo.js** - Both of these will apply appropriate syntaxy highlighting to the specific note.
+
+### installation
+
+1) add the code as the notes contents.
+
+2) set "note type" to "JS frontend"
+
+3) add an "owned attribute" of "#widget"
+
+### use
+
+on the note you want highlighting set the "owned attributes" to "#todotxt #hideHighlightWidget" or "#moodlog #hideHighlightWidget" depending on which syntax type you want to highlight
+
+## bidirectional sync
+
+**trilium_sync.php** - script to keep a folder of local text files and a trilium note in sync.
+
+### installation
+
+Edit the config values at the top of the script to match your trilium setup. Run it manually to test it is working as expected. If it is create a cron job.
+
+### example use
+
+```
+$> php trilium_sync.php
+[i] checking connections
+[i] version: x.xx.x
+[i] gathering info
+
+- Folders -
+Folder/Path Folder Name Parent noteId Mod Trl Mod Dsk Status
+Synced/ Synced root j0DRZhIzV97W 1742502936 1742507367 Exists
+Synced/BothPlaces/ BothPlaces j0DRZhIzV97W AQb3Dg0yZHSu 1742507075 1742507200 Exists
+Synced/[del] tobe del/ [del] tobe del j0DRZhIzV97W j0xUs1FdZL1Y 1742507321 Missing_Disk
+Synced/AddToTrilium/ AddToTrilium 1742507383 Missing_Trilium
+Synced/tobe deleted/ tobe deleted 1742507200 Missing_Trilium
+- Files -
+Folder/Path Filename Parent noteId Mod Trl Mod Dsk Hash Status
+Synced/BothPlaces/ UpdateDiskFile AQb3Dg0yZHSu IwUZLwWdbiWG 1742507314 1742507199 Diff Update_Disk
+Synced/BothPlaces/ UpdateTriliumFile AQb3Dg0yZHSu 7pMPgsb9AVRf 1742507130 1742507345 Diff Update_Trilium
+Synced/[del] tobe del/ gone_also.txt j0xUs1FdZL1Y 4wOoHDy07csr 1742507178 N/A Missing_Disk
+Synced/AddToTrilium/ fromDiskToTrilium.txt 1742507394 N/A Missing_Trilium
+Synced/tobe deleted/ gone_also.txt 1742507200 N/A Missing_Trilium
+
+[-] deleting disk folder: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/tobe deleted/
+[+] creating trilium folder: Synced/AddToTrilium/
+[+] creating trilium note: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/AddToTrilium/fromDiskToTrilium.txt
+[+] updating file on disk: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/BothPlaces/UpdateDiskFile
+[+] updating file on trilium: /mnt/hgfs/PentestOS/Misc/trilium_sync/Synced/BothPlaces/UpdateTriliumFile
+```
\ No newline at end of file
diff --git a/highlight_moodlog.js b/highlight_moodlog.js
new file mode 100644
index 0000000..be4c283
--- /dev/null
+++ b/highlight_moodlog.js
@@ -0,0 +1,165 @@
+const TPL = `
+ Moodlog Highlighting Applied
+
`;
+
+class MoodlogHighlighterWidget extends api.NoteContextAwareWidget {
+ static get parentWidget() {
+ //console.log("MDLG: Getting parent widget: center-pane");
+ return 'center-pane';
+ }
+
+ get position() {
+ //console.log("MDLG: Setting position: 100");
+ return 100;
+ }
+
+ isEnabled() {
+ const enabled = super.isEnabled()
+ && this.note.type === 'text'
+ && this.note.hasLabel('moodlog');
+ //console.log(`MDLG: isEnabled: ${enabled}`);
+ return enabled;
+ }
+
+ doRender() {
+ //console.log("MDLG: Rendering widget...");
+ this.$widget = $(TPL);
+ //console.log("MDLG: Widget rendered:", this.$widget);
+ return this.$widget;
+ }
+
+ async refreshWithNote(note) {
+ //console.log("MDLG: Refreshing with note:", note);
+ const { content } = await note.getNoteComplement();
+ //console.log("MDLG: Note content fetched:", content);
+
+ // Check if content is fetched properly
+ if (!content) {
+ //console.log("MDLG: No content fetched. Exiting refresh.");
+ return;
+ }
+
+ const highlightedContent = this.applyHighlighting(content);
+ //console.log("MDLG: Highlighted content:", highlightedContent);
+
+ if (highlightedContent !== content) {
+ //console.log("MDLG: Updating note content with highlighted version");
+
+ // Use the setNoteContent function to update the note content
+ try {
+ await setNoteContent(note.noteId, highlightedContent);
+ //console.log("MDLG: Content updated with highlighted version");
+ } catch (error) {
+ //console.error("MDLG: Error updating content:", error);
+ }
+ } else {
+ //console.log("MDLG: No changes to content, skipping update.");
+ }
+ }
+
+
+ applyHighlighting(content) {
+ //console.log("MDLG: Applying highlighting to content...");
+
+ // Replace with \n, then remove all other HTML tags
+ const plainTextContent = content.replace(/ /gi, '\n').replace(/<[^>]*>/g, ' ');
+ //console.log("MDLG: Stripped content:", plainTextContent);
+
+ // Combined regex for date & time
+ const dateTimePattern = /([1-2]\d{3}[-/.](0?[1-9]|1[0-2])[-/.](0[1-9]|[12]\d|3[01]))\s+(\d{1,2}:\d{2})/g;
+ const projectPattern = /\[\S+?\]/g;
+ const contextPattern = /(\s|^)(@\S+)/g;
+ const hashtagPattern = /(\s|^)(#\S+)/g;
+ const positivePattern = /(\s|^)(\+\S+)/g;
+ const negativePattern = /(\s|^)(-\S+)/g;
+
+ // Escape HTML characters to prevent issues with replacements
+ function escapeHtml(str) {
+ return str.replace(/[&<>"']/g, function (char) {
+ return {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": '''
+ }[char];
+ });
+ }
+
+ // Helper function to map the number to a colour based on the gradient
+ function getProjectColour(number) {
+ const minColor = { r: 107, g: 109, b: 229 }; // #6b6de5
+ const maxColor = { r: 70, g: 226, b: 96 }; // #46e260
+
+ const ratio = number / 9; // since we are working with 0-9
+
+ // Interpolate the RGB values between minColor and maxColor
+ const r = Math.round(minColor.r + ratio * (maxColor.r - minColor.r));
+ const g = Math.round(minColor.g + ratio * (maxColor.g - minColor.g));
+ const b = Math.round(minColor.b + ratio * (maxColor.b - minColor.b));
+
+ return `rgb(${r}, ${g}, ${b})`;
+ }
+
+ // Process each line of the plain text content
+ let highlightedLines = plainTextContent.split("\n").map(line => {
+ //console.log("MDLG: Processing line:", line);
+
+ // Apply combined date & time highlight
+ line = line.replace(dateTimePattern, `$1$4`);
+
+ // Apply other highlights
+ line = line.replace(projectPattern, (match) => {
+ // Extract the number inside square brackets
+ const number = parseInt(match.match(/\d+/)[0], 10);
+ const colour = getProjectColour(number);
+ return `${escapeHtml(match)}`;
+ });
+
+ // Apply other highlights
+ //line = line.replace(projectPattern, `$&`);
+ line = line.replace(contextPattern, (_, space, tag) => `${space}${escapeHtml(tag)}`);
+ line = line.replace(hashtagPattern, (_, space, tag) => `${space}${escapeHtml(tag)}`);
+ line = line.replace(positivePattern, (_, space, tag) => `${space}${escapeHtml(tag)}`);
+ line = line.replace(negativePattern, (_, space, tag) => `${space}${escapeHtml(tag)}`);
+
+ return line.trim();
+ });
+
+ // Join lines with proper handling
+ let finalContent = highlightedLines.join(" ");
+
+ //console.log("MDLG: Highlighted content:", finalContent);
+ return finalContent;
+}
+
+
+
+
+ async entitiesReloadedEvent({ loadResults }) {
+ //console.log("MDLG: Entities reloaded event triggered");
+ if (loadResults.isNoteContentReloaded(this.noteId)) {
+ //console.log("MDLG: Note content reloaded, refreshing widget...");
+ //this.refresh();
+ } else {
+ //console.log("MDLG: No content reload detected.");
+ }
+ }
+
+
+}
+
+console.log("MDLG: Starting Moodlog Highlighter Widget");
+module.exports = MoodlogHighlighterWidget;
+
+function setNoteContent(noteId, content) {
+ /*const noteElement = document.querySelector(`note-detail-readonly-text-content`);
+
+ if (noteElement) {
+ noteElement.innerHTML = content; // Update the displayed content
+ console.log(`MDLG: Updated note ${noteId} in the DOM.`);
+ } else {
+ console.warn(`MDLG: Note element with data-note-id='${noteId}' not found.`);
+ }*/
+ return api.runOnBackend((id, data) => api.getNote(id).setContent(data), [noteId, content]);
+}
diff --git a/highlight_todotxt.js b/highlight_todotxt.js
new file mode 100644
index 0000000..1f1b007
--- /dev/null
+++ b/highlight_todotxt.js
@@ -0,0 +1,157 @@
+const TPL = `
+ Todo Highlighting Applied
+
`;
+
+class MoodlogHighlighterWidget extends api.NoteContextAwareWidget {
+ static get parentWidget() {
+ //console.log("MDLG: Getting parent widget: center-pane");
+ return 'center-pane';
+ }
+
+ get position() {
+ //console.log("MDLG: Setting position: 100");
+ return 100;
+ }
+
+ isEnabled() {
+ const enabled = super.isEnabled()
+ && this.note.type === 'text'
+ && this.note.hasLabel('todotxt');
+ //console.log(`MDLG: isEnabled: ${enabled}`);
+ return enabled;
+ }
+
+ doRender() {
+ //console.log("MDLG: Rendering widget...");
+ this.$widget = $(TPL);
+ //console.log("MDLG: Widget rendered:", this.$widget);
+ return this.$widget;
+ }
+
+ async refreshWithNote(note) {
+ //console.log("MDLG: Refreshing with note:", note);
+ const { content } = await note.getNoteComplement();
+ //console.log("MDLG: Note content fetched:", content);
+
+ // Check if content is fetched properly
+ if (!content) {
+ //console.log("MDLG: No content fetched. Exiting refresh.");
+ return;
+ }
+
+ const highlightedContent = this.applyHighlighting(content);
+ //console.log("MDLG: Highlighted content:", highlightedContent);
+
+ if (highlightedContent !== content) {
+ //console.log("MDLG: Updating note content with highlighted version");
+
+ // Use the setNoteContent function to update the note content
+ try {
+ await setNoteContent(note.noteId, highlightedContent);
+ //console.log("MDLG: Content updated with highlighted version");
+ } catch (error) {
+ //console.error("MDLG: Error updating content:", error);
+ }
+ } else {
+ //console.log("MDLG: No changes to content, skipping update.");
+ }
+ }
+
+
+applyHighlighting(content) {
+ // Replace with \n, then remove all other HTML tags
+ const plainTextContent = content.replace(/ /gi, '\n').replace(/<[^>]*>/g, ' ');
+
+ // Regular expressions for Todo.txt syntax
+ const datePattern = /\b(0[1-9]|[12]\d|3[01])\/(0[1-9]|1[0-2])\/([1-2]\d{3})\b/g;
+ const completedTaskPattern = /^\s*x\s+.*/g;
+ const priorityPattern = /^(\s*\([A-E]\))\s+/;
+ const projectPattern = /(?:\s|^)(\+\S+)/g;
+ const contextPattern = /(?:\s|^)(@\S+)/g;
+ const waitPattern = /\bWAIT\b/gi;
+ const customAttributePattern = /(\s+[^\s:]+:[^\s:]+)+\s*$/g;
+
+ function escapeHtml(str) {
+ return str.replace(/[&<>"']/g, char => ({
+ '&': '&', '<': '<', '>': '>',
+ '"': '"', "'": '''
+ })[char]);
+ }
+
+ let highlightedLines = plainTextContent.split("\n").map(line => {
+ // If line starts with 'x', make the whole line grey
+ if (/^\s*x\s+/.test(line)) {
+ return `${line}`;
+ }
+
+ // Highlight priority with different colours based on level
+ line = line.replace(priorityPattern, (match, priority) => {
+ const level = priority.match(/[A-E]/)?.[0];
+ const colors = {
+ 'A': 'rgb(255, 0, 0)', // Red
+ 'B': 'rgb(255, 102, 0)', // Orange
+ 'C': 'rgb(255, 204, 0)', // Yellow
+ 'D': 'rgb(0, 153, 255)', // Blue
+ 'E': 'rgb(102, 204, 102)' // Green
+ };
+ const color = colors[level] || 'black';
+ return `${escapeHtml(priority)} `;
+ });
+
+ // Highlight date
+ line = line.replace(datePattern, (match) => {
+ return `${escapeHtml(match)}`;
+ });
+
+ // Highlight project tags
+ line = line.replace(projectPattern, (_, tag) => ` ${escapeHtml(tag)}`);
+
+ // Highlight context tags
+ line = line.replace(contextPattern, (_, tag) => ` ${escapeHtml(tag)}`);
+
+ // Highlight WAIT command
+ line = line.replace(waitPattern, `WAIT`);
+
+ // Highlight custom attributes
+ line = line.replace(customAttributePattern, `$1`);
+
+ // Remove leading space (if any) from the beginning of the line, without affecting spaces before + or @
+ line = line.replace(/^\s+/, '');
+
+ return line.trim();
+ });
+
+ return highlightedLines.join(" ");
+}
+
+
+
+
+
+ async entitiesReloadedEvent({ loadResults }) {
+ //console.log("MDLG: Entities reloaded event triggered");
+ if (loadResults.isNoteContentReloaded(this.noteId)) {
+ //console.log("MDLG: Note content reloaded, refreshing widget...");
+ //this.refresh();
+ } else {
+ //console.log("MDLG: No content reload detected.");
+ }
+ }
+
+
+}
+
+console.log("TodoHighlight: Starting todotxt Highlighter Widget");
+module.exports = MoodlogHighlighterWidget;
+
+function setNoteContent(noteId, content) {
+ /*const noteElement = document.querySelector(`note-detail-readonly-text-content`);
+
+ if (noteElement) {
+ noteElement.innerHTML = content; // Update the displayed content
+ console.log(`MDLG: Updated note ${noteId} in the DOM.`);
+ } else {
+ console.warn(`MDLG: Note element with data-note-id='${noteId}' not found.`);
+ }*/
+ return api.runOnBackend((id, data) => api.getNote(id).setContent(data), [noteId, content]);
+}
diff --git a/htb2trilium/INSTALL.md b/htb2trilium/INSTALL.md
new file mode 100644
index 0000000..da8afad
--- /dev/null
+++ b/htb2trilium/INSTALL.md
@@ -0,0 +1,41 @@
+## 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:Difficulty=promoted,single,text #label:Solved=promoted,single,text #label:Released=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/htb2trilium/README.md b/htb2trilium/README.md
new file mode 100644
index 0000000..1b45ae2
--- /dev/null
+++ b/htb2trilium/README.md
@@ -0,0 +1,16 @@
+htb2trilium
+===============
+
+Pull hackthebox info and stats into personal trilium note taking software
+
+single machine view:
+
+
+
+overview of machines:
+
+
+
+navigation:
+
+
\ No newline at end of file
diff --git a/htb2trilium/config.json b/htb2trilium/config.json
new file mode 100644
index 0000000..39597ce
--- /dev/null
+++ b/htb2trilium/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/htb2trilium_challenges.py b/htb2trilium/htb2trilium_challenges.py
new file mode 100644
index 0000000..d0d95ca
--- /dev/null
+++ b/htb2trilium/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'].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)))
diff --git a/htb2trilium/htb2trilium_machines.py b/htb2trilium/htb2trilium_machines.py
new file mode 100644
index 0000000..de8be53
--- /dev/null
+++ b/htb2trilium/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 = "No"
+ yes = "Yes"
+ 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 = """
+
+
+
+