update conditions to support extras options.
This commit is contained in:
parent
fca82b3cc9
commit
0d6532df5d
4 changed files with 177 additions and 8 deletions
|
|
@ -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":
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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', {
|
||||
|
|
|
|||
2
ui/app/types/conditions.d.ts
vendored
2
ui/app/types/conditions.d.ts
vendored
|
|
@ -3,7 +3,7 @@ export type ConditionItem = {
|
|||
name: string
|
||||
filter: string
|
||||
cli: string
|
||||
[key: string]: any
|
||||
extras: Record<string, any>
|
||||
}
|
||||
|
||||
export type ImportedConditionItem = ConditionItem & {
|
||||
|
|
|
|||
Loading…
Reference in a new issue