From 55b2e3e7f245c7a269b90205917c56e513d0918b Mon Sep 17 00:00:00 2001 From: arabcoders Date: Tue, 3 Jun 2025 20:05:13 +0300 Subject: [PATCH] Add test button for conditions --- app/library/HttpAPI.py | 68 ++++++++++++++++++++++++++++++++++++ app/library/conditions.py | 23 ++++++++++++ ui/composables/useConfirm.ts | 8 +++-- ui/pages/conditions.vue | 49 +++++++++++++++++++++++--- 4 files changed, 141 insertions(+), 7 deletions(-) diff --git a/app/library/HttpAPI.py b/app/library/HttpAPI.py index b97c8dd1..7d050ac3 100644 --- a/app/library/HttpAPI.py +++ b/app/library/HttpAPI.py @@ -871,6 +871,74 @@ class HttpAPI(Common): await self._notify.emit(Events.CONDITIONS_UPDATE, data=items) return web.json_response(data=items, status=web.HTTPOk.status_code, dumps=self.encoder.encode) + @route("POST", "api/conditions/test") + async def conditions_test(self, request: Request) -> Response: + """ + Test condition against URL. + + Args: + request (Request): The request object. + + Returns: + Response: The response object + + """ + data = await request.json() + + if not isinstance(data, dict): + return web.json_response( + {"error": "Invalid request body expecting dict."}, + status=web.HTTPBadRequest.status_code, + ) + + url = data.get("url") + if not url: + return web.json_response({"error": "url is required."}, status=web.HTTPBadRequest.status_code) + + cond = data.get("condition") + if not cond: + return web.json_response({"error": "condition name is required."}, status=web.HTTPBadRequest.status_code) + + try: + opts = {} + + if ytdlp_proxy := self.config.get_ytdlp_args().get("proxy", None): + opts["proxy"] = ytdlp_proxy + + preset = data.get("preset", self.config.default_preset) + ytdlp_opts = YTDLPOpts.get_instance().preset(name=preset, with_cookies=True).add(opts).get_all() + key = self.cache.hash(url + str(preset)) + if not self.cache.has(key): + data = extract_info( + config=ytdlp_opts, + url=url, + debug=False, + no_archive=True, + follow_redirect=True, + sanitize_info=True, + ) + self.cache.set(key=key, value=data, ttl=600) + else: + data = self.cache.get(key) + except Exception as e: + LOG.exception(e) + return web.json_response( + data={"error": f"Failed to extract video info. '{e!s}'"}, + status=web.HTTPInternalServerError.status_code, + ) + + matched = Conditions.get_instance().single_match(name=cond, info=data) + if matched is None: + return web.json_response( + data={"error": f"Condition '{cond}' doesn't match the data from the URL."}, + status=web.HTTPNotFound.status_code, + ) + + return web.json_response( + data={"message": f"Condition '{cond}' matched the data from the URL."}, + status=web.HTTPOk.status_code, + ) + @route("GET", "api/presets") async def presets(self, request: Request) -> Response: """ diff --git a/app/library/conditions.py b/app/library/conditions.py index b91beba1..cb764052 100644 --- a/app/library/conditions.py +++ b/app/library/conditions.py @@ -317,3 +317,26 @@ class Conditions(metaclass=Singleton): continue return None + + def single_match(self, name: str, info: dict) -> Condition | None: + """ + Check if condition matches the info dict. + + Args: + name (str): The condition name to check. + info (dict): The info dict to check. + + Returns: + Condition|None: The condition if found, None otherwise. + + """ + if len(self._items) < 1 or not info or not isinstance(info, dict) or len(info) < 1: + return None + + item = self.get(name) + if not item or not item.filter: + return None + + from yt_dlp.utils import match_str + + return item if match_str(item.filter, info) else None diff --git a/ui/composables/useConfirm.ts b/ui/composables/useConfirm.ts index 45976a4d..90ed293d 100644 --- a/ui/composables/useConfirm.ts +++ b/ui/composables/useConfirm.ts @@ -14,6 +14,10 @@ function alert(msg: string): boolean { return window.confirm(msg) } -export default function useConfirm() { - return { confirm, alert, reduceConfirm } +function prompt(msg: string, defaultValue: string = ''): string | null { + return window.prompt(msg, defaultValue) +} + +export default function useConfirm() { + return { confirm, alert, prompt, reduceConfirm } } diff --git a/ui/pages/conditions.vue b/ui/pages/conditions.vue index 96433fa9..72694b2a 100644 --- a/ui/pages/conditions.vue +++ b/ui/pages/conditions.vue @@ -66,7 +66,7 @@
-
{{ filterItem(cond) }}
+
{{ JSON.stringify(cleanObject(cond, remove_keys), null, 2) }}
+ @@ -104,6 +111,10 @@ filter i am able to bypass it availability = 'needs_auth' & channel_id = 'channel_id'. and set proxy for that specific video, while leaving the rest of the videos to be downloaded normally. +
  • + The data which the filter is applied on is the same data that yt-dlp returns, simply, click on the + information button, and check the data to craft your filter. +
  • @@ -125,6 +136,7 @@ const toggleForm = ref(false) const isLoading = ref(false) const initialLoad = ref(true) const addInProgress = ref(false) +const remove_keys = ['in_progress', 'raw'] watch(() => config.app.basic_mode, async () => { if (!config.app.basic_mode) { @@ -236,6 +248,7 @@ const deleteItem = async (item) => { } const updateItem = async ({ reference, item }) => { + item = cleanObject(item, remove_keys) if (reference) { const index = items.value.findIndex(t => t?.id === reference) if (index > -1) { @@ -254,10 +267,7 @@ const updateItem = async ({ reference, item }) => { resetForm(true) } -const filterItem = item => { - const { raw, ...rest } = item - return JSON.stringify(rest, null, 2) -} +const filterItem = i => JSON.stringify(cleanObject(i, remove_keys), null, 2) const editItem = _item => { item.value = _item @@ -287,4 +297,33 @@ const exportItem = item => { return copyText(base64UrlEncode(JSON.stringify(userData))) } + +const TestCondition = async (item) => { + const url = box.prompt("Enter URL to test condition against:") + if (!url) { + return + } + + item.in_progress = true + + try { + const response = await request("/api/conditions/test", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ url: url, condition: item.id || item.name }), + }) + + const data = await response.json() + if (!response.ok) { + toast.error(`Failed to test condition. ${data.error}`) + return + } + + toast.success(data?.message || "Condition tested successfully.") + } catch (e) { + toast.error(`Failed to test condition. ${e.message}`) + } finally { + item.in_progress = false + } +}