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
|
cli: str
|
||||||
"""If matched append this to the download request and retry."""
|
"""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:
|
def serialize(self) -> dict:
|
||||||
return self.__dict__
|
return self.__dict__
|
||||||
|
|
||||||
|
|
@ -137,6 +140,10 @@ class Conditions(metaclass=Singleton):
|
||||||
item["id"] = str(uuid.uuid4())
|
item["id"] = str(uuid.uuid4())
|
||||||
need_save = True
|
need_save = True
|
||||||
|
|
||||||
|
if "extras" not in item:
|
||||||
|
item["extras"] = {}
|
||||||
|
need_save = True
|
||||||
|
|
||||||
item: Condition = init_class(Condition, item)
|
item: Condition = init_class(Condition, item)
|
||||||
|
|
||||||
self._items.append(item)
|
self._items.append(item)
|
||||||
|
|
@ -145,7 +152,7 @@ class Conditions(metaclass=Singleton):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if need_save:
|
if need_save:
|
||||||
LOG.info("Saving conditions due to format, or id change.")
|
LOG.info("Saving conditions due changes.")
|
||||||
self.save(self._items)
|
self.save(self._items)
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
@ -213,6 +220,10 @@ class Conditions(metaclass=Singleton):
|
||||||
msg = f"Invalid command options for yt-dlp. '{e!s}'."
|
msg = f"Invalid command options for yt-dlp. '{e!s}'."
|
||||||
raise ValueError(msg) from e
|
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
|
return True
|
||||||
|
|
||||||
def save(self, items: list[Condition | dict]) -> "Conditions":
|
def save(self, items: list[Condition | dict]) -> "Conditions":
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,60 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -231,6 +285,8 @@ const config = useConfigStore()
|
||||||
|
|
||||||
const form = reactive<ConditionItem>(JSON.parse(JSON.stringify(props.item)))
|
const form = reactive<ConditionItem>(JSON.parse(JSON.stringify(props.item)))
|
||||||
const import_string = ref('')
|
const import_string = ref('')
|
||||||
|
const newExtraKey = ref('')
|
||||||
|
const newExtraValue = ref('')
|
||||||
const test_data = ref<{
|
const test_data = ref<{
|
||||||
show: boolean,
|
show: boolean,
|
||||||
url: string,
|
url: string,
|
||||||
|
|
@ -241,6 +297,11 @@ const test_data = ref<{
|
||||||
const showOptions = ref<boolean>(false)
|
const showOptions = ref<boolean>(false)
|
||||||
const ytDlpOpt = ref<AutoCompleteOptions>([])
|
const ytDlpOpt = ref<AutoCompleteOptions>([])
|
||||||
|
|
||||||
|
// Initialize extras if not present
|
||||||
|
if (!form.extras) {
|
||||||
|
form.extras = {}
|
||||||
|
}
|
||||||
|
|
||||||
watch(() => config.ytdlp_options, newOptions => ytDlpOpt.value = newOptions
|
watch(() => config.ytdlp_options, newOptions => ytDlpOpt.value = newOptions
|
||||||
.filter(opt => !opt.ignored)
|
.filter(opt => !opt.ignored)
|
||||||
.flatMap(opt => opt.flags
|
.flatMap(opt => opt.flags
|
||||||
|
|
@ -272,10 +333,10 @@ const checkInfo = async (): Promise<void> => {
|
||||||
const copy: ConditionItem = JSON.parse(JSON.stringify(form))
|
const copy: ConditionItem = JSON.parse(JSON.stringify(form))
|
||||||
|
|
||||||
for (const key in copy) {
|
for (const key in copy) {
|
||||||
if (typeof copy[key] !== 'string') {
|
if (typeof (copy as any)[key] !== 'string') {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
copy[key] = copy[key].trim()
|
(copy as any)[key] = (copy as any)[key].trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
emitter('submit', { reference: toRaw(props.reference), item: toRaw(copy) })
|
emitter('submit', { reference: toRaw(props.reference), item: toRaw(copy) })
|
||||||
|
|
@ -343,7 +404,7 @@ const importItem = async (): Promise<void> => {
|
||||||
return
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -359,6 +420,10 @@ const importItem = async (): Promise<void> => {
|
||||||
form.cli = item.cli
|
form.cli = item.cli
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item.extras) {
|
||||||
|
form.extras = { ...item.extras }
|
||||||
|
}
|
||||||
|
|
||||||
import_string.value = ''
|
import_string.value = ''
|
||||||
showImport.value = false
|
showImport.value = false
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
|
@ -391,4 +456,95 @@ const logic_test = computed(() => {
|
||||||
return false
|
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>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -119,13 +119,15 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ConditionItem, ImportedConditionItem } from '~/types/conditions'
|
import type { ConditionItem, ImportedConditionItem } from '~/types/conditions'
|
||||||
|
|
||||||
|
type ConditionItemWithUI = ConditionItem & { raw?: boolean }
|
||||||
|
|
||||||
const toast = useNotification()
|
const toast = useNotification()
|
||||||
const config = useConfigStore()
|
const config = useConfigStore()
|
||||||
const socket = useSocketStore()
|
const socket = useSocketStore()
|
||||||
const box = useConfirm()
|
const box = useConfirm()
|
||||||
const isMobile = useMediaQuery({ maxWidth: 1024 })
|
const isMobile = useMediaQuery({ maxWidth: 1024 })
|
||||||
|
|
||||||
const items = ref<ConditionItem[]>([])
|
const items = ref<ConditionItemWithUI[]>([])
|
||||||
const item = ref<Partial<ConditionItem>>({})
|
const item = ref<Partial<ConditionItem>>({})
|
||||||
const itemRef = ref<string | null | undefined>("")
|
const itemRef = ref<string | null | undefined>("")
|
||||||
const toggleForm = ref(false)
|
const toggleForm = ref(false)
|
||||||
|
|
@ -188,12 +190,12 @@ const updateItems = async (newItems: ConditionItem[]): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
addInProgress.value = true
|
addInProgress.value = true
|
||||||
|
|
||||||
const validItems = newItems.map(({ id, name, filter, cli }) => {
|
const validItems = newItems.map(({ id, name, filter, cli, extras }) => {
|
||||||
if (!name || !filter || !cli) {
|
if (!name || !filter || !cli) {
|
||||||
toast.error('Name, filter and cli are required.')
|
toast.error('Name, filter and cli are required.')
|
||||||
throw new Error('Missing fields')
|
throw new Error('Missing fields')
|
||||||
}
|
}
|
||||||
return { id, name, filter, cli }
|
return { id, name, filter, cli, extras }
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await request('/api/conditions', {
|
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
|
name: string
|
||||||
filter: string
|
filter: string
|
||||||
cli: string
|
cli: string
|
||||||
[key: string]: any
|
extras: Record<string, any>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ImportedConditionItem = ConditionItem & {
|
export type ImportedConditionItem = ConditionItem & {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue