diff --git a/.gitignore b/.gitignore index 664f934..31742dd 100644 --- a/.gitignore +++ b/.gitignore @@ -39,7 +39,8 @@ logs/ # Ignore macOS system files .DS_Store -# Ignore uploads +# Ignore these direct uploads/ - .vscode/ +config/ +json-schema/ diff --git a/json-schema/README.md b/json-schema/README.md deleted file mode 100644 index d43cc26..0000000 --- a/json-schema/README.md +++ /dev/null @@ -1,38 +0,0 @@ -JSON schemas for Kometa YAML files - -How to: - -Add this as the first line in your `config.yml`: -``` -# yaml-language-server: $schema=https://raw.githubusercontent.com/Kometa-Team/Kometa/nightly/json-schema/config-schema.json -``` -[change `nightly` to `develop`, or `master` if you wish] - -Then open your config file in an editor that supports the use of JSON schema. - -For example, VS Code with the Red Hat YAML extension. - -This will give you context-sensitive hints and auto-complete for much of the Kometa `config.yml` - -![image](https://github.com/Kometa-Team/Kometa/assets/3865541/62133e59-ed12-4764-a4da-23595824d4da) - -![image](https://github.com/Kometa-Team/Kometa/assets/3865541/06fbca9b-f0ad-4c20-8cf0-12d6c259c838) - -limitations: - -- template variables not cased for specific default file -- template variables with keys are wildcarded -- "position" attribute has no validation -- "streaming" default has no validation -- search has no validation; just accepts string -- schedule has no validation; just accepts string - -TODO: -"list of coordinates" - -- schema for collection yaml -- schema for metadata yaml -- schema for overlay yaml -- schema for template yaml - -Notes: diff --git a/modules/helpers.py b/modules/helpers.py index 7c7b5c9..f3ecdb3 100644 --- a/modules/helpers.py +++ b/modules/helpers.py @@ -1,3 +1,4 @@ +import hashlib import os import re import requests @@ -15,6 +16,91 @@ } +JSON_SCHEMA_DIR = "json-schema" +FILES_TO_DOWNLOAD = ["prototype_config.yml", "config-schema.json"] +GITHUB_BASE_URL = "https://raw.githubusercontent.com/Kometa-Team/Kometa" + +HASH_FILE = os.path.join( + JSON_SCHEMA_DIR, "file_hashes.txt" +) # Stores previous file hashes + + +def get_kometa_branch(): + """Fetch the correct branch (master or nightly).""" + from .helpers import check_for_update # Prevent circular import + + version_info = check_for_update() + return version_info.get("kometa_branch", "nightly") # Default to nightly + + +def calculate_hash(content): + """Compute the SHA256 hash of the given content.""" + return hashlib.sha256(content.encode("utf-8")).hexdigest() + + +def load_previous_hashes(): + """Load the last known hashes of schema files.""" + if not os.path.exists(HASH_FILE): + return {} + + hashes = {} + with open(HASH_FILE, "r", encoding="utf-8") as f: + for line in f: + filename, file_hash = line.strip().split(":", 1) + hashes[filename] = file_hash + return hashes + + +def save_hashes(hashes): + """Save updated hashes to the hash file.""" + with open(HASH_FILE, "w", encoding="utf-8") as f: + for filename, file_hash in hashes.items(): + f.write(f"{filename}:{file_hash}\n") + + +def ensure_json_schema(): + """Ensure json-schema files exist and are up-to-date based on hash checks.""" + branch = get_kometa_branch() + + if not os.path.exists(JSON_SCHEMA_DIR): + os.makedirs(JSON_SCHEMA_DIR) + + previous_hashes = load_previous_hashes() + new_hashes = {} + + for filename in FILES_TO_DOWNLOAD: + file_path = os.path.join(JSON_SCHEMA_DIR, filename) + url = f"{GITHUB_BASE_URL}/{branch}/json-schema/{filename}" + + # print(f"[INFO] Checking for updates: {filename}...") + + try: + response = requests.get(url, timeout=10) + response.raise_for_status() + new_content = response.text + new_hash = calculate_hash(new_content) + + # Compare hash with previous version + if filename in previous_hashes and previous_hashes[filename] == new_hash: + # print(f"[INFO] No changes detected for {filename}. Skipping download.") + new_hashes[filename] = new_hash # Keep existing hash + continue + + # Save the new file if hash has changed + # print(f"[INFO] Changes detected in {filename}. Downloading new version...") + with open(file_path, "w", encoding="utf-8") as f: + f.write(new_content) + + new_hashes[filename] = new_hash + + except requests.RequestException as e: + print(f"[ERROR] Failed to download {filename}: {e}") + continue # Skip to the next file + + # Save updated hashes + save_hashes(new_hashes) + + def get_local_version(): """Read the local VERSION file and determine the branch.""" version_file = "VERSION" @@ -43,16 +129,20 @@ def get_remote_version(branch): def check_for_update(): - """Compare the local version with the remote version.""" + """Compare the local version with the remote version and determine Kometa branch.""" local_version, branch = get_local_version() remote_version = get_remote_version(branch) update_available = remote_version and remote_version != local_version + # Determine Kometa branch + kometa_branch = "master" if branch == "master" else "nightly" + return { "local_version": local_version, "remote_version": remote_version, "branch": branch, + "kometa_branch": kometa_branch, "update_available": update_available, } diff --git a/modules/output.py b/modules/output.py index 2910122..22f2aa9 100644 --- a/modules/output.py +++ b/modules/output.py @@ -16,7 +16,9 @@ build_config_dict, get_template_list, get_bits, + check_for_update, enforce_string_fields, + ensure_json_schema, STRING_FIELDS, ) @@ -406,12 +408,19 @@ def build_config(header_style="ascii"): yaml.default_flow_style = False yaml.sort_keys = False + ensure_json_schema() + with open("json-schema/config-schema.json", "r") as file: schema = yaml.load(file) - # Prepare the final YAML content + # Fetch kometa_branch dynamically + version_info = check_for_update() + kometa_branch = version_info.get( + "kometa_branch", "nightly" + ) # Default to nightly if not found + yaml_content = ( - "# yaml-language-server: $schema=https://raw.githubusercontent.com/Kometa-Team/Kometa/nightly/json-schema/config-schema.json\n\n" + f"# yaml-language-server: $schema=https://raw.githubusercontent.com/Kometa-Team/Kometa/{kometa_branch}/json-schema/config-schema.json\n\n" f"{section_heading('KOMETA') if header_style == 'ascii' else ('#==================== KOMETA ====================#' if header_style == 'divider' else '')}\n\n" f"{header_comment}\n\n" ) diff --git a/modules/persistence.py b/modules/persistence.py index 8594e36..4723c8f 100644 --- a/modules/persistence.py +++ b/modules/persistence.py @@ -4,7 +4,13 @@ from ruamel.yaml import YAML from flask import current_app as app -from .helpers import build_config_dict, get_template_list, get_bits, booler +from .helpers import ( + build_config_dict, + get_template_list, + get_bits, + booler, + ensure_json_schema, +) from .iso_639_1 import iso_639_1_languages # Importing the languages list from .iso_639_2 import iso_639_2_languages # Importing the languages list from .iso_3166_1 import iso_3166_1_regions # Importing the regions list @@ -202,6 +208,9 @@ def retrieve_status(target): def get_dummy_data(target): yaml = YAML(typ="safe", pure=True) + + ensure_json_schema() + with open("json-schema/prototype_config.yml", "r") as file: base_config = yaml.load(file) diff --git a/quickstart.py b/quickstart.py index a9fbc6f..0e74c73 100644 --- a/quickstart.py +++ b/quickstart.py @@ -43,6 +43,9 @@ redact_sensitive_data, check_for_update, update_checker_loop, + booler, + is_default_image, + ensure_json_schema, ) from modules.persistence import ( save_settings, @@ -51,9 +54,7 @@ flush_session_storage, notification_systems_available, ) -from modules.database import reset_data -from modules.database import get_unique_config_names -from modules.helpers import booler, is_default_image +from modules.database import reset_data, get_unique_config_names from PIL import Image, ImageDraw @@ -111,6 +112,9 @@ def inject_version_info(): server_session = Session(app) +# Ensure json-schema files are up to date at startup +ensure_json_schema() + @app.route("/check_base_images", methods=["GET"]) def check_base_images(): diff --git a/templates/001-navigation.html b/templates/001-navigation.html index 8a02309..720719f 100644 --- a/templates/001-navigation.html +++ b/templates/001-navigation.html @@ -12,10 +12,10 @@

{{ page_info['title'] }}

{% if page_info['template_name'] != '001-start' %} - {% endif %} @@ -63,7 +63,7 @@

{{ page_info['title'] }}

{% endif %} diff --git a/templates/025-libraries.html b/templates/025-libraries.html index 20db6d8..6061859 100644 --- a/templates/025-libraries.html +++ b/templates/025-libraries.html @@ -49,9 +49,9 @@

- +
@@ -82,9 +82,9 @@

@@ -97,9 +97,9 @@

@@ -112,9 +112,9 @@

@@ -127,9 +127,9 @@

@@ -142,9 +142,9 @@

@@ -157,9 +157,9 @@

@@ -172,9 +172,9 @@

@@ -187,9 +187,9 @@

@@ -202,9 +202,9 @@

@@ -217,9 +217,9 @@

@@ -232,9 +232,9 @@

@@ -247,9 +247,9 @@

@@ -262,9 +262,9 @@

@@ -277,9 +277,9 @@

@@ -292,9 +292,9 @@

@@ -324,9 +324,9 @@

@@ -357,9 +357,9 @@

@@ -372,9 +372,9 @@

@@ -386,9 +386,9 @@

@@ -400,9 +400,9 @@

@@ -415,9 +415,9 @@

@@ -429,9 +429,9 @@

@@ -443,9 +443,9 @@

@@ -457,9 +457,9 @@

@@ -488,9 +488,9 @@

- +
@@ -500,9 +500,9 @@

@@ -513,9 +513,9 @@

@@ -525,9 +525,9 @@

@@ -537,9 +537,9 @@

@@ -568,9 +568,9 @@

@@ -580,9 +580,9 @@

@@ -592,9 +592,9 @@

@@ -604,9 +604,9 @@

@@ -616,9 +616,9 @@

@@ -629,9 +629,9 @@

@@ -641,9 +641,9 @@

@@ -671,9 +671,9 @@

@@ -683,9 +683,9 @@

- +
@@ -695,9 +695,9 @@

@@ -724,9 +724,9 @@

@@ -736,9 +736,9 @@

@@ -748,9 +748,9 @@

@@ -760,9 +760,9 @@

@@ -790,9 +790,9 @@

@@ -803,9 +803,9 @@

- +
@@ -832,9 +832,9 @@

- +
@@ -845,9 +845,9 @@

@@ -858,9 +858,9 @@

@@ -871,9 +871,9 @@

- +
@@ -901,9 +901,9 @@

- +
@@ -913,9 +913,9 @@

- +
@@ -925,9 +925,9 @@

- +
@@ -1003,9 +1003,9 @@

- +
@@ -1032,9 +1032,9 @@

@@ -1061,9 +1061,9 @@

name="mov-content-rating-group" value="us_movie" {% if data['libraries']['mov-attribute_selected_content_rating'] == 'us_movie' %}checked{% endif %}> @@ -1072,9 +1072,9 @@

name="mov-content-rating-group" value="uk" {% if data['libraries']['mov-attribute_selected_content_rating'] == 'uk' %}checked{% endif %}> @@ -1083,9 +1083,9 @@

name="mov-content-rating-group" value="de" {% if data['libraries']['mov-attribute_selected_content_rating'] == 'de' %}checked{% endif %}> @@ -1094,9 +1094,9 @@

name="mov-content-rating-group" value="au" {% if data['libraries']['mov-attribute_selected_content_rating'] == 'au' %}checked{% endif %}> @@ -1105,9 +1105,9 @@

name="mov-content-rating-group" value="nz" {% if data['libraries']['mov-attribute_selected_content_rating'] == 'nz' %}checked{% endif %}> @@ -1116,9 +1116,9 @@

name="mov-content-rating-group" value="commonsense" {% if data['libraries']['mov-attribute_selected_content_rating'] == 'commonsense' %}checked{% endif %}> @@ -1147,9 +1147,9 @@

@@ -1203,9 +1203,9 @@

@@ -1231,9 +1231,9 @@

- +
@@ -1243,9 +1243,9 @@

@@ -1290,9 +1290,9 @@

- +
@@ -1319,9 +1319,9 @@

@@ -1417,7 +1417,7 @@

{% if data['libraries']['mov-attribute_remove_overlays']|string|lower == 'true' %}checked{% endif %}>

- Reset Overlays + Reset Overlays title="Using Reset Overlays or frequently Removing and Reapplying overlays will quickly result in Image Bloat. Neither Plex nor Kometa cleans up previously-used posters with overlays. Consider using ImageMaid to clean up old, unused images."> - Using ImageMaid can help clean up old, unused images. + Using ImageMaid can help clean up old, unused images.

@@ -1520,9 +1520,9 @@

@@ -1554,9 +1554,9 @@

@@ -1569,9 +1569,9 @@

@@ -1584,9 +1584,9 @@

@@ -1598,9 +1598,9 @@

@@ -1628,9 +1628,9 @@

@@ -1661,9 +1661,9 @@

@@ -1676,9 +1676,9 @@

@@ -1690,9 +1690,9 @@

@@ -1704,9 +1704,9 @@

@@ -1719,9 +1719,9 @@

@@ -1733,9 +1733,9 @@

@@ -1746,9 +1746,9 @@

@@ -1777,9 +1777,9 @@

- +
@@ -1789,9 +1789,9 @@

@@ -1802,9 +1802,9 @@

@@ -1814,9 +1814,9 @@

@@ -1826,9 +1826,9 @@

@@ -1857,9 +1857,9 @@

@@ -1869,9 +1869,9 @@

@@ -1881,9 +1881,9 @@

@@ -1893,9 +1893,9 @@

@@ -1905,9 +1905,9 @@

@@ -1917,9 +1917,9 @@

@@ -1929,9 +1929,9 @@

@@ -1959,9 +1959,9 @@

@@ -1971,9 +1971,9 @@

- +
@@ -1983,9 +1983,9 @@

@@ -2012,9 +2012,9 @@

@@ -2024,9 +2024,9 @@

@@ -2036,9 +2036,9 @@

@@ -2048,9 +2048,9 @@

@@ -2079,9 +2079,9 @@

- +
@@ -2091,9 +2091,9 @@

@@ -2104,9 +2104,9 @@

- +
@@ -2133,9 +2133,9 @@

- +
@@ -2163,9 +2163,9 @@

- +
@@ -2175,9 +2175,9 @@

- +
@@ -2249,9 +2249,9 @@

- +
@@ -2278,9 +2278,9 @@

@@ -2290,9 +2290,9 @@

- +
@@ -2319,9 +2319,9 @@

name="sho-content-rating-group" value="us_show" {% if data['libraries']['sho-attribute_selected_content_rating'] == 'us_show' %}checked{% endif %}> @@ -2330,9 +2330,9 @@

name="sho-content-rating-group" value="uk" {% if data['libraries']['sho-attribute_selected_content_rating'] == 'uk' %}checked{% endif %}> @@ -2341,9 +2341,9 @@

name="sho-content-rating-group" value="de" {% if data['libraries']['sho-attribute_selected_content_rating'] == 'de' %}checked{% endif %}> @@ -2352,9 +2352,9 @@

name="sho-content-rating-group" value="au" {% if data['libraries']['sho-attribute_selected_content_rating'] == 'au' %}checked{% endif %}> @@ -2363,9 +2363,9 @@

name="sho-content-rating-group" value="nz" {% if data['libraries']['sho-attribute_selected_content_rating'] == 'nz' %}checked{% endif %}> @@ -2374,9 +2374,9 @@

name="sho-content-rating-group" value="commonsense" {% if data['libraries']['sho-attribute_selected_content_rating'] == 'commonsense' %}checked{% endif %}> @@ -2405,9 +2405,9 @@

@@ -2461,9 +2461,9 @@

@@ -2489,9 +2489,9 @@

- +
@@ -2501,9 +2501,9 @@

@@ -2546,9 +2546,9 @@

@@ -2559,9 +2559,9 @@

- +
@@ -2588,9 +2588,9 @@

@@ -2686,7 +2686,7 @@

{% if data['libraries']['sho-attribute_remove_overlays']|string|lower == 'true' %}checked{% endif %}>

- + Reset Overlays title="Using Reset Overlays or frequently Removing and Reapplying overlays will quickly result in Image Bloat. Neither Plex nor Kometa cleans up previously-used posters with overlays. Consider using ImageMaid to clean up old, unused images."> - Using ImageMaid can help clean up old, unused images. + Using ImageMaid can help clean up old, unused images.

diff --git a/templates/modals/001-start.html b/templates/modals/001-start.html index c6c0b00..caac854 100644 --- a/templates/modals/001-start.html +++ b/templates/modals/001-start.html @@ -9,16 +9,16 @@

Completing the {{page_info['

Kometa is your ultimate solution for managing and enhancing your media server experience. Whether you're a seasoned pro or just getting started, Kometa offers powerful features to help you organize your media collections effortlessly. With Kometa, you can create stunning overlays, customize your media presentation, and ensure your server stands out. Join the Kometa community today and take your media server to the next level!

-

For more information, visit our wiki.

+

For more information, visit our wiki.

Example Collections After Using Kometa Quickstart
- Kometa Default Collections + Kometa Default Collections
Example Ribbon Overlays
- Kometa Ribbon + Kometa Ribbon
diff --git a/templates/modals/030-tautulli.html b/templates/modals/030-tautulli.html index 8532004..72ecfc2 100644 --- a/templates/modals/030-tautulli.html +++ b/templates/modals/030-tautulli.html @@ -2,5 +2,5 @@
This section is optional
Enter your Tautulli URL and API Key and press "Validate".
Alternatively, press the green arrow button to skip ahead. -

Click Here to go to the Kometa wiki for this connector. +

Click Here to go to the Kometa wiki for this connector.
diff --git a/templates/modals/040-github.html b/templates/modals/040-github.html index e7c17e0..48ebaf7 100644 --- a/templates/modals/040-github.html +++ b/templates/modals/040-github.html @@ -2,5 +2,5 @@

This section is optional
Enter your GitHub Personal Access Token and press "Validate".
Alternatively, press the green arrow button to skip ahead. -

Click Here to go to the Kometa wiki for this connector. +

Click Here to go to the Kometa wiki for this connector. diff --git a/templates/modals/050-omdb.html b/templates/modals/050-omdb.html index 8c85443..2c6f32f 100644 --- a/templates/modals/050-omdb.html +++ b/templates/modals/050-omdb.html @@ -2,5 +2,5 @@

This section is optional
Enter your OMDb API Key and press "Validate".
Alternatively, press the green arrow button to skip ahead. -

Click Here to go to the Kometa wiki for this connector. +

Click Here to go to the Kometa wiki for this connector. diff --git a/templates/modals/060-mdblist.html b/templates/modals/060-mdblist.html index 08784d7..fef9c64 100644 --- a/templates/modals/060-mdblist.html +++ b/templates/modals/060-mdblist.html @@ -2,5 +2,5 @@

This section is optional
Enter your MDBList API Key and press "Validate".
Alternatively, press the green arrow button to skip ahead. -

Click Here to go to the Kometa wiki for this connector. +

Click Here to go to the Kometa wiki for this connector. diff --git a/templates/modals/070-notifiarr.html b/templates/modals/070-notifiarr.html index 756d9d6..8f1d642 100644 --- a/templates/modals/070-notifiarr.html +++ b/templates/modals/070-notifiarr.html @@ -2,5 +2,5 @@
This section is optional
Enter your Notifiarr API Key and press "Validate".
Alternatively, press the green arrow button to skip ahead. -

Click Here to go to the Kometa wiki for this connector. +

Click Here to go to the Kometa wiki for this connector. diff --git a/templates/modals/080-gotify.html b/templates/modals/080-gotify.html index f985f65..50d4b4e 100644 --- a/templates/modals/080-gotify.html +++ b/templates/modals/080-gotify.html @@ -2,5 +2,5 @@
This section is optional
Enter your Gotify URL/API Key and press "Validate".
Alternatively, press the green arrow button to skip ahead. -

Click Here to go to the Kometa wiki for this connector. +

Click Here to go to the Kometa wiki for this connector. diff --git a/templates/modals/085-ntfy.html b/templates/modals/085-ntfy.html index b067ebb..a7d1d81 100644 --- a/templates/modals/085-ntfy.html +++ b/templates/modals/085-ntfy.html @@ -2,5 +2,5 @@
This section is optional
Enter your ntfy URL/Access Token/Topic and press "Validate".
Alternatively, press the green arrow button to skip ahead. -

Click Here to go to the Kometa wiki for this connector. +

Click Here to go to the Kometa wiki for this connector. diff --git a/templates/modals/090-webhooks.html b/templates/modals/090-webhooks.html index 7cf71d4..c968c81 100644 --- a/templates/modals/090-webhooks.html +++ b/templates/modals/090-webhooks.html @@ -2,5 +2,5 @@
This section is optional
Enter your preferred client for each webhook.
Alternatively, press the green arrow button to skip ahead. -

Click Here to go to the Kometa wiki for this connector. +

Click Here to go to the Kometa wiki for this connector. diff --git a/templates/modals/100-anidb.html b/templates/modals/100-anidb.html index 18e3256..8120338 100644 --- a/templates/modals/100-anidb.html +++ b/templates/modals/100-anidb.html @@ -2,5 +2,5 @@
This section is optional
Enter your AniDB Client/Username/Password and press "Validate".
Alternatively, press the green arrow button to skip ahead. -

Click Here to go to the Kometa wiki for this connector. +

Click Here to go to the Kometa wiki for this connector. diff --git a/templates/modals/110-radarr.html b/templates/modals/110-radarr.html index 50d2c3c..57a219d 100644 --- a/templates/modals/110-radarr.html +++ b/templates/modals/110-radarr.html @@ -2,5 +2,5 @@
This section is optional
Enter your Radarr URL/Token and press "Validate", then complete the rest of the form.
Alternatively, press the green arrow button to skip ahead. -

Click Here to go to the Kometa wiki for this connector. +

Click Here to go to the Kometa wiki for this connector. diff --git a/templates/modals/120-sonarr.html b/templates/modals/120-sonarr.html index 4420632..5f14f7d 100644 --- a/templates/modals/120-sonarr.html +++ b/templates/modals/120-sonarr.html @@ -2,5 +2,5 @@
This section is optional
Enter your Sonarr URL/Token and press "Validate", then complete the rest of the form.
Alternatively, press the green arrow button to skip ahead. -

Click Here to go to the Kometa wiki for this connector. +

Click Here to go to the Kometa wiki for this connector. diff --git a/templates/modals/130-trakt.html b/templates/modals/130-trakt.html index d1ed69a..c5d385b 100644 --- a/templates/modals/130-trakt.html +++ b/templates/modals/130-trakt.html @@ -2,5 +2,5 @@
This section is optional
Enter your Trakt Client ID/Client Secret and press "Retrieve PIN" which will take you to Trakt to complete authorization. Copy the PIN and paste it into the form, then press "Validate PIN".
Alternatively, press the green arrow button to skip ahead. -

Click Here to go to the Kometa wiki for this connector. +

Click Here to go to the Kometa wiki for this connector. diff --git a/templates/modals/140-mal.html b/templates/modals/140-mal.html index db7307d..777c034 100644 --- a/templates/modals/140-mal.html +++ b/templates/modals/140-mal.html @@ -2,5 +2,5 @@
This section is optional
Enter your MyAnimeList Client ID/Client Secret and press "Authorize" which will take you to MyAnimeList to complete authorization. you will be taken to a localhost page which does not load. Copy that URL and paste it into the form and then press "Complete Authentication".
Alternatively, press the green arrow button to skip ahead. -

Click Here to go to the Kometa wiki for this connector. +

Click Here to go to the Kometa wiki for this connector. diff --git a/templates/modals/150-settings.html b/templates/modals/150-settings.html index 471b8ea..87b1052 100644 --- a/templates/modals/150-settings.html +++ b/templates/modals/150-settings.html @@ -1,5 +1,5 @@ diff --git a/templates/modals/900-final.html b/templates/modals/900-final.html index 0ef2f17..226e404 100644 --- a/templates/modals/900-final.html +++ b/templates/modals/900-final.html @@ -16,7 +16,7 @@

Next Steps

If your Configuration File is ready to go, hit the Download Config button and place it in the config folder of your Kometa directory.

-

Once your Configuration File is in the correct location, we suggest you do a manual run of Kometa so that you can view the results. You can use the Run Immediately flag to do an immediate Kometa run.

+

Once your Configuration File is in the correct location, we suggest you do a manual run of Kometa so that you can view the results. You can use the Run Immediately flag to do an immediate Kometa run.

Thanks for using Quickstart and enjoy your Kometa journey!