diff --git a/app/library/conditions.py b/app/library/conditions.py
index 76eed61d..100b9a14 100644
--- a/app/library/conditions.py
+++ b/app/library/conditions.py
@@ -30,6 +30,9 @@ class Condition:
cli: str
"""If matched append this to the download request and retry."""
+ extras: dict[str, Any] = field(default_factory=dict)
+ """Any extra data to store with the condition."""
+
def serialize(self) -> dict:
return self.__dict__
@@ -137,6 +140,10 @@ class Conditions(metaclass=Singleton):
item["id"] = str(uuid.uuid4())
need_save = True
+ if "extras" not in item:
+ item["extras"] = {}
+ need_save = True
+
item: Condition = init_class(Condition, item)
self._items.append(item)
@@ -145,7 +152,7 @@ class Conditions(metaclass=Singleton):
continue
if need_save:
- LOG.info("Saving conditions due to format, or id change.")
+ LOG.info("Saving conditions due changes.")
self.save(self._items)
return self
@@ -213,6 +220,10 @@ class Conditions(metaclass=Singleton):
msg = f"Invalid command options for yt-dlp. '{e!s}'."
raise ValueError(msg) from e
+ if not isinstance(item.get("extras"), dict):
+ msg = "Extras must be a dictionary."
+ raise ValueError(msg) # noqa: TRY004
+
return True
def save(self, items: list[Condition | dict]) -> "Conditions":
diff --git a/ui/app/components/ConditionForm.vue b/ui/app/components/ConditionForm.vue
index b29bc385..4059c84e 100644
--- a/ui/app/components/ConditionForm.vue
+++ b/ui/app/components/ConditionForm.vue
@@ -107,6 +107,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ For advanced users only. This feature is meant to be expanded later. the only supported key right
+ now. ignore_download with value of true. Keys must be lowercase with
+ underscores (e.g., custom_field).
+
+
+
@@ -231,6 +285,8 @@ const config = useConfigStore()
const form = reactive(JSON.parse(JSON.stringify(props.item)))
const import_string = ref('')
+const newExtraKey = ref('')
+const newExtraValue = ref('')
const test_data = ref<{
show: boolean,
url: string,
@@ -241,6 +297,11 @@ const test_data = ref<{
const showOptions = ref(false)
const ytDlpOpt = ref([])
+// Initialize extras if not present
+if (!form.extras) {
+ form.extras = {}
+}
+
watch(() => config.ytdlp_options, newOptions => ytDlpOpt.value = newOptions
.filter(opt => !opt.ignored)
.flatMap(opt => opt.flags
@@ -272,10 +333,10 @@ const checkInfo = async (): Promise => {
const copy: ConditionItem = JSON.parse(JSON.stringify(form))
for (const key in copy) {
- if (typeof copy[key] !== 'string') {
+ if (typeof (copy as any)[key] !== 'string') {
continue
}
- copy[key] = copy[key].trim()
+ (copy as any)[key] = (copy as any)[key].trim()
}
emitter('submit', { reference: toRaw(props.reference), item: toRaw(copy) })
@@ -343,7 +404,7 @@ const importItem = async (): Promise => {
return
}
- if ((form.filter || form.cli) && !(await box.confirm('Overwrite the current form fields?', true))) {
+ if ((form.filter || form.cli || Object.keys(form.extras).length > 0) && !(await box.confirm('Overwrite the current form fields?', true))) {
return
}
@@ -359,6 +420,10 @@ const importItem = async (): Promise => {
form.cli = item.cli
}
+ if (item.extras) {
+ form.extras = { ...item.extras }
+ }
+
import_string.value = ''
showImport.value = false
} catch (e: any) {
@@ -391,4 +456,95 @@ const logic_test = computed(() => {
return false
}
})
+
+// Extras management functions
+const validateKey = (key: string): boolean => {
+ // Key must be lowercase with underscores only
+ return /^[a-z][a-z0-9_]*$/.test(key)
+}
+
+const parseValue = (value: string): string | number | boolean => {
+ // Try to parse as number
+ if (!isNaN(Number(value)) && !isNaN(parseFloat(value))) {
+ return Number(value)
+ }
+
+ // Try to parse as boolean
+ if (value.toLowerCase() === 'true') {
+ return true
+ }
+ if (value.toLowerCase() === 'false') {
+ return false
+ }
+
+ // Return as string
+ return value
+}
+
+const addExtra = (): void => {
+ const key = newExtraKey.value.trim()
+ const value = newExtraValue.value.trim()
+
+ if (!key || !value) {
+ toast.error('Both key and value are required.')
+ return
+ }
+
+ if (!validateKey(key)) {
+ toast.error('Key must be lowercase and contain only letters, numbers, and underscores (e.g., custom_field).')
+ return
+ }
+
+ if (form.extras[key]) {
+ toast.error(`Key '${key}' already exists.`)
+ return
+ }
+
+ form.extras[key] = parseValue(value)
+ newExtraKey.value = ''
+ newExtraValue.value = ''
+}
+
+const removeExtra = (key: string): void => {
+ delete form.extras[key]
+}
+
+const updateExtraKey = (event: Event, oldKey: string): void => {
+ const target = event.target as HTMLInputElement
+ const newKey = target.value.trim()
+
+ if (!newKey) {
+ return
+ }
+
+ if (!validateKey(newKey)) {
+ toast.error('Key must be lowercase and contain only letters, numbers, and underscores.')
+ target.value = oldKey // Reset to old value
+ return
+ }
+
+ if (newKey !== oldKey) {
+ if (form.extras[newKey]) {
+ toast.error(`Key '${newKey}' already exists.`)
+ target.value = oldKey // Reset to old value
+ return
+ }
+
+ // Move the value to the new key
+ const value = form.extras[oldKey]
+ delete form.extras[oldKey]
+ form.extras[newKey] = value
+ }
+}
+
+const updateExtraValue = (key: string, event: Event): void => {
+ const target = event.target as HTMLInputElement
+ const value = target.value.trim()
+
+ if (value) {
+ form.extras[key] = parseValue(value)
+ } else {
+ form.extras[key] = ''
+ }
+}
diff --git a/ui/app/pages/conditions.vue b/ui/app/pages/conditions.vue
index 9dca9c90..1e1af7dc 100644
--- a/ui/app/pages/conditions.vue
+++ b/ui/app/pages/conditions.vue
@@ -119,13 +119,15 @@