update conditions to support extras options.

This commit is contained in:
arabcoders 2025-09-08 16:50:47 +03:00
parent fca82b3cc9
commit 0d6532df5d
4 changed files with 177 additions and 8 deletions

View file

@ -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":

View file

@ -107,6 +107,60 @@
</span>
</div>
</div>
<div class="column is-12">
<div class="field">
<label class="label is-inline">
<span class="icon"><i class="fa-solid fa-plus-square" /></span>
Extra Options
</label>
<div class="content">
<div v-if="form.extras && Object.keys(form.extras).length > 0" class="mb-3">
<div v-for="(value, key) in form.extras" :key="key" class="field is-grouped mb-2">
<div class="control">
<input type="text" class="input" :value="key" @input="updateExtraKey($event, key)"
:disabled="addInProgress" placeholder="key_name">
</div>
<div class="control is-expanded">
<input type="text" class="input" :value="value" @input="updateExtraValue(key, $event)"
:disabled="addInProgress" placeholder="value">
</div>
<div class="control">
<button type="button" class="button is-danger" @click="removeExtra(key)"
:disabled="addInProgress">
<span class="icon"><i class="fa-solid fa-times" /></span>
</button>
</div>
</div>
</div>
<div class="field is-grouped">
<div class="control">
<input type="text" class="input" v-model="newExtraKey" :disabled="addInProgress"
placeholder="new_key" @keyup.enter="addExtra">
</div>
<div class="control is-expanded">
<input type="text" class="input" v-model="newExtraValue" :disabled="addInProgress"
placeholder="new_value" @keyup.enter="addExtra">
</div>
<div class="control">
<button type="button" class="button is-primary" @click="addExtra"
:disabled="addInProgress || !newExtraKey || !newExtraValue">
<span class="icon"><i class="fa-solid fa-plus" /></span>
<span>Add</span>
</button>
</div>
</div>
</div>
<span class="help is-bold">
<span class="icon"><i class="fa-solid fa-info" /></span>
<span>
For advanced users only. This feature is meant to be expanded later. the only supported key right
now. <code>ignore_download</code> with value of <code>true</code>. Keys must be lowercase with
underscores (e.g., custom_field).</span>
</span>
</div>
</div>
</div>
</div>
@ -231,6 +285,8 @@ const config = useConfigStore()
const form = reactive<ConditionItem>(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<boolean>(false)
const ytDlpOpt = ref<AutoCompleteOptions>([])
// 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<void> => {
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<void> => {
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<void> => {
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] = ''
}
}
</script>

View file

@ -119,13 +119,15 @@
<script setup lang="ts">
import type { ConditionItem, ImportedConditionItem } from '~/types/conditions'
type ConditionItemWithUI = ConditionItem & { raw?: boolean }
const toast = useNotification()
const config = useConfigStore()
const socket = useSocketStore()
const box = useConfirm()
const isMobile = useMediaQuery({ maxWidth: 1024 })
const items = ref<ConditionItem[]>([])
const items = ref<ConditionItemWithUI[]>([])
const item = ref<Partial<ConditionItem>>({})
const itemRef = ref<string | null | undefined>("")
const toggleForm = ref(false)
@ -188,12 +190,12 @@ const updateItems = async (newItems: ConditionItem[]): Promise<boolean> => {
try {
addInProgress.value = true
const validItems = newItems.map(({ id, name, filter, cli }) => {
const validItems = newItems.map(({ id, name, filter, cli, extras }) => {
if (!name || !filter || !cli) {
toast.error('Name, filter and cli are required.')
throw new Error('Missing fields')
}
return { id, name, filter, cli }
return { id, name, filter, cli, extras }
})
const response = await request('/api/conditions', {

View file

@ -3,7 +3,7 @@ export type ConditionItem = {
name: string
filter: string
cli: string
[key: string]: any
extras: Record<string, any>
}
export type ImportedConditionItem = ConditionItem & {