From d0a11cbcd61ef55ca9f1f447514ea4798dec149e Mon Sep 17 00:00:00 2001
From: arabcoders
Date: Mon, 8 Sep 2025 17:25:36 +0300
Subject: [PATCH] make it possible to ignore item download via conditions
---
app/library/DownloadQueue.py | 55 +++++++++++++++++++++++++++--
app/library/conditions.py | 15 ++++----
ui/app/components/ConditionForm.vue | 32 +++++------------
ui/app/pages/conditions.vue | 10 +++++-
4 files changed, 77 insertions(+), 35 deletions(-)
diff --git a/app/library/DownloadQueue.py b/app/library/DownloadQueue.py
index 5652f549..bcfbfebd 100644
--- a/app/library/DownloadQueue.py
+++ b/app/library/DownloadQueue.py
@@ -24,6 +24,7 @@ from .Presets import Presets
from .Scheduler import Scheduler
from .Singleton import Singleton
from .Utils import (
+ archive_add,
arg_converter,
calc_download_path,
dt_delta,
@@ -675,8 +676,10 @@ class DownloadQueue(metaclass=Singleton):
LOG.warning(f"Using external downloader '{yt_conf.get('external_downloader')}' for '{item.url}'.")
item.extras.update({"external_downloader": True})
+ archive_id = item.get_archive_id()
+
if item.is_archived():
- if archive_id := item.get_archive_id():
+ if archive_id:
store_type, _ = self.get_item(archive_id=archive_id)
if not store_type:
dlInfo = Download(
@@ -750,7 +753,55 @@ class DownloadQueue(metaclass=Singleton):
if not item.requeued and (condition := Conditions.get_instance().match(info=entry)):
already.pop()
- LOG.info(f"Matched '{condition.name}' for '{item.url}' Adding '{condition.cli}' to request.")
+
+ item_title = entry.get("title") or entry.get("id") or item.url
+ message = f"Condition '{condition.name}' matched for '{item_title}'."
+
+ if condition.cli:
+ message += f" Re-queuing with '{condition.cli}'."
+
+ LOG.info(message)
+
+ if condition.extras.get("ignore_download", False):
+ extra_msg: str = ""
+ if yt_conf.get("download_archive") and not condition.extras.get("no_archive", False):
+ archive_add(yt_conf.get("download_archive"), [archive_id])
+ extra_msg = f" and added to archive '{yt_conf.get('download_archive')}'"
+
+ log_message = f"Ignoring download of '{item_title}' as per condition '{condition.name}'{extra_msg}."
+
+ store_type, _ = self.get_item(archive_id=archive_id)
+ if not store_type:
+ dlInfo = Download(
+ info=ItemDTO(
+ id=entry.get("id"),
+ title=item_title,
+ url=item.url,
+ preset=item.preset,
+ folder=item.folder,
+ status="skip",
+ cookies=item.cookies,
+ template=item.template,
+ msg=log_message,
+ extras=item.extras,
+ )
+ )
+ self.done.put(dlInfo)
+
+ LOG.info(log_message)
+ await asyncio.gather(
+ *[
+ self._notify.emit(Events.LOG_INFO, data={}, title="Ignored Download", message=log_message),
+ self._notify.emit(
+ Events.ITEM_MOVED,
+ data={"to": "history", "preset": dlInfo.info.preset, "item": dlInfo.info},
+ title="Download History Update",
+ message=f"Download history updated with '{item.url}'.",
+ ),
+ ]
+ )
+ return {"status": "ok"}
+
return await self.add(item=item.new_with(requeued=True, cli=condition.cli), already=already)
_status, _msg = ytdlp_reject(entry=entry, yt_params=yt_conf)
diff --git a/app/library/conditions.py b/app/library/conditions.py
index 100b9a14..a86267e8 100644
--- a/app/library/conditions.py
+++ b/app/library/conditions.py
@@ -210,15 +210,12 @@ class Conditions(metaclass=Singleton):
msg = f"Invalid filter. '{e!s}'."
raise ValueError(msg) from e
- if not item.get("cli"):
- msg = "No command options for yt-dlp were found."
- raise ValueError(msg)
-
- try:
- arg_converter(args=item.get("cli"))
- except Exception as e:
- msg = f"Invalid command options for yt-dlp. '{e!s}'."
- raise ValueError(msg) from e
+ if item.get("cli"):
+ try:
+ arg_converter(args=item.get("cli"))
+ except Exception as e:
+ 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."
diff --git a/ui/app/components/ConditionForm.vue b/ui/app/components/ConditionForm.vue
index 4059c84e..12f726c7 100644
--- a/ui/app/components/ConditionForm.vue
+++ b/ui/app/components/ConditionForm.vue
@@ -304,8 +304,7 @@ if (!form.extras) {
watch(() => config.ytdlp_options, newOptions => ytDlpOpt.value = newOptions
.filter(opt => !opt.ignored)
- .flatMap(opt => opt.flags
- .filter(flag => flag.startsWith('--'))
+ .flatMap(opt => opt.flags.filter(flag => flag.startsWith('--'))
.map(flag => ({ value: flag, description: opt.description || '' }))),
{ immediate: true }
)
@@ -313,7 +312,7 @@ watch(() => config.ytdlp_options, newOptions => ytDlpOpt.value = newOptions
watch(() => form.filter, () => test_data.value.changed = true)
const checkInfo = async (): Promise => {
- const required: (keyof ConditionItem)[] = ['name', 'filter', 'cli']
+ const required: (keyof ConditionItem)[] = ['name', 'filter']
for (const key of required) {
if (!form[key]) {
@@ -457,27 +456,20 @@ const logic_test = computed(() => {
}
})
-// Extras management functions
-const validateKey = (key: string): boolean => {
- // Key must be lowercase with underscores only
- return /^[a-z][a-z0-9_]*$/.test(key)
-}
+const validateKey = (key: string): boolean => /^[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') {
+ if ('true' === value.toLowerCase()) {
return true
}
- if (value.toLowerCase() === 'false') {
+ if ('false' === value.toLowerCase()) {
return false
}
- // Return as string
return value
}
@@ -491,7 +483,7 @@ const addExtra = (): void => {
}
if (!validateKey(key)) {
- toast.error('Key must be lowercase and contain only letters, numbers, and underscores (e.g., custom_field).')
+ toast.error('Key must be lower_case.')
return
}
@@ -519,18 +511,17 @@ const updateExtraKey = (event: Event, oldKey: string): void => {
if (!validateKey(newKey)) {
toast.error('Key must be lowercase and contain only letters, numbers, and underscores.')
- target.value = oldKey // Reset to old value
+ target.value = oldKey
return
}
if (newKey !== oldKey) {
if (form.extras[newKey]) {
toast.error(`Key '${newKey}' already exists.`)
- target.value = oldKey // Reset to old value
+ target.value = oldKey
return
}
- // Move the value to the new key
const value = form.extras[oldKey]
delete form.extras[oldKey]
form.extras[newKey] = value
@@ -540,11 +531,6 @@ const updateExtraKey = (event: Event, oldKey: string): void => {
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] = ''
- }
+ form.extras[key] = value ? parseValue(value) : ''
}
diff --git a/ui/app/pages/conditions.vue b/ui/app/pages/conditions.vue
index 1e1af7dc..3e236c9b 100644
--- a/ui/app/pages/conditions.vue
+++ b/ui/app/pages/conditions.vue
@@ -64,6 +64,14 @@
{{ cond.cli }}
+
+
+ Extras:
+
+ {{ key }}: {{ value }}
+
+
+
@@ -278,7 +286,7 @@ const exportItem = (cond: ConditionItem): void => {
const userData: ImportedConditionItem = {
...Object.fromEntries(Object.entries(clone).filter(([_, v]) => !!v)),
_type: 'condition',
- _version: '1.0',
+ _version: '1.1',
} as ImportedConditionItem
copyText(encode(userData))