Merge pull request #256 from arabcoders/dev

When using external downloader show indicator in the WebUI
This commit is contained in:
Abdulmohsen 2025-04-10 01:38:01 +03:00 committed by GitHub
commit 53da40b5f5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 93 additions and 11 deletions

View file

@ -184,8 +184,8 @@ class DownloadQueue(metaclass=Singleton):
""" """
if item.has_extras(): if item.has_extras():
for key in item.extras.copy(): for key in item.extras.copy():
if not str(key).startswith("playlist"): if not self.keep_extra_key(key):
item.extras.pop(key, None) item.extras.pop(key)
if not entry: if not entry:
return {"status": "error", "msg": "Invalid/empty data was given."} return {"status": "error", "msg": "Invalid/empty data was given."}
@ -395,6 +395,10 @@ class DownloadQueue(metaclass=Singleton):
.get_all(), .get_all(),
} }
if yt_conf.get("external_downloader"):
LOG.warning(f"Using external downloader '{yt_conf.get('external_downloader')}' for '{item.url}'.")
item.extras.update({"external_downloader": True})
downloaded, id_dict = self._is_downloaded(file=yt_conf.get("download_archive", None), url=item.url) downloaded, id_dict = self._is_downloaded(file=yt_conf.get("download_archive", None), url=item.url)
if downloaded is True and id_dict: if downloaded is True and id_dict:
message = f"This url with ID '{id_dict.get('id')}' has been downloaded already and recorded in archive." message = f"This url with ID '{id_dict.get('id')}' has been downloaded already and recorded in archive."
@ -715,3 +719,17 @@ class DownloadQueue(metaclass=Singleton):
return False, None return False, None
return is_downloaded(file, url) return is_downloaded(file, url)
def keep_extra_key(self, key: str) -> bool:
"""
Check if the extra key should be kept.
Args:
key (str): The extra key to check.
Returns:
bool: True if the extra key should be kept, False otherwise.
"""
keys = ("playlist", "external_downloader")
return any(key == k or key.startswith(k) for k in keys)

View file

@ -134,6 +134,12 @@ class Item:
preset = item.get("preset") preset = item.get("preset")
if preset and isinstance(preset, str) and preset != Item._default_preset(): if preset and isinstance(preset, str) and preset != Item._default_preset():
from .Presets import Presets
if not Presets.get_instance().has(preset):
msg = f"Preset '{preset}' does not exist."
raise ValueError(msg)
data["preset"] = preset data["preset"] = preset
if item.get("folder") and isinstance(item.get("folder"), str): if item.get("folder") and isinstance(item.get("folder"), str):

View file

@ -46,12 +46,14 @@ class TargetRequest:
method: str method: str
url: str url: str
headers: list[TargetRequestHeader] = field(default_factory=list) headers: list[TargetRequestHeader] = field(default_factory=list)
data_key: str = "data"
def serialize(self) -> dict: def serialize(self) -> dict:
return { return {
"type": self.type, "type": self.type,
"method": self.method, "method": self.method,
"url": self.url, "url": self.url,
"data_key": self.data_key,
"headers": [h.serialize() for h in self.headers], "headers": [h.serialize() for h in self.headers],
} }
@ -213,7 +215,8 @@ class Notification(metaclass=Singleton):
try: try:
Notification.validate(target) Notification.validate(target)
except ValueError as e: except ValueError as e:
LOG.error(f"Invalid notification target '{target}'. '{e!s}'") name = target.get("name") or target.get("id") or target.get("request", {}).get("url") or "unknown"
LOG.error(f"Invalid notification target '{name}'. '{e!s}'")
continue continue
target = self.make_target(target) target = self.make_target(target)
@ -247,6 +250,7 @@ class Notification(metaclass=Singleton):
type=target.get("request", {}).get("type", "json"), type=target.get("request", {}).get("type", "json"),
method=target.get("request", {}).get("method", "POST"), method=target.get("request", {}).get("method", "POST"),
url=target.get("request", {}).get("url"), url=target.get("request", {}).get("url"),
data_key=target.get("request", {}).get("data_key", "data"),
headers=[ headers=[
TargetRequestHeader( TargetRequestHeader(
key=str(h.get("key", "")).strip(), key=str(h.get("key", "")).strip(),
@ -288,6 +292,9 @@ class Notification(metaclass=Singleton):
msg = "Invalid notification target. No URL found." msg = "Invalid notification target. No URL found."
raise ValueError(msg) raise ValueError(msg)
if "data_key" not in target["request"]:
target["request"]["data_key"] = "data"
if "method" in target["request"] and target["request"]["method"].upper() not in ["POST", "PUT"]: if "method" in target["request"] and target["request"]["method"].upper() not in ["POST", "PUT"]:
msg = "Invalid notification target. Invalid method found." msg = "Invalid notification target. Invalid method found."
raise ValueError(msg) raise ValueError(msg)
@ -363,10 +370,17 @@ class Notification(metaclass=Singleton):
for h in target.request.headers: for h in target.request.headers:
reqBody["headers"][h.key] = h.value reqBody["headers"][h.key] = h.value
reqBody["json" if "json" == target.request.type.lower() else "data"] = self._deep_unpack(ev.serialize()) body_key = "json" if "json" == target.request.type.lower() else "data"
reqBody[body_key] = self._deep_unpack(ev.serialize())
if "data" != target.request.data_key:
reqBody[body_key][target.request.data_key] = reqBody[body_key]["data"]
reqBody[body_key].pop("data", None)
if "form" == target.request.type.lower(): if "form" == target.request.type.lower():
reqBody["data"]["data"] = self._encoder.encode(reqBody["data"]["data"]) reqBody[body_key][target.request.data_key] = self._encoder.encode(
reqBody[body_key][target.request.data_key]
)
response = await self._client.request(**reqBody) response = await self._client.request(**reqBody)

View file

@ -266,6 +266,19 @@ class Presets(metaclass=Singleton):
return self return self
def has(self, id_or_name: str) -> bool:
"""
Check if the preset exists by id or name.
Args:
id_or_name (str): The id or name of the preset.
Returns:
bool: True if the preset exists, False otherwise.
"""
return self.get(id_or_name) is not None
def get(self, id_or_name: str) -> Preset | None: def get(self, id_or_name: str) -> Preset | None:
""" """
Get the preset by id or name. Get the preset by id or name.

View file

@ -138,7 +138,7 @@
</div> </div>
</div> </div>
<div class="column is-12 is-clearfix"> <div class="column is-6-tablet is-12-mobile">
<div class="field"> <div class="field">
<label class="label is-inline" for="on"> <label class="label is-inline" for="on">
Select Events Select Events
@ -167,6 +167,26 @@
</div> </div>
</div> </div>
<div class="column is-6-tablet is-12-mobile">
<div class="field">
<label class="label is-inline" for="data_key">
Data field
</label>
<div class="control has-icons-left">
<input type="text" class="input" id="data_key" v-model="form.request.data_key"
:disabled="addInProgress" required>
<span class="icon is-small is-left"><i class="fa-solid fa-key" /></span>
</div>
<span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span>
<span>
The field name to use when sending the notification. This is used to identify the data in the
request. The default is <code>data</code>.
</span>
</span>
</div>
</div>
<div class="column is-12"> <div class="column is-12">
<div class="field"> <div class="field">
<label class="label is-inline is-unselectable"> <label class="label is-inline is-unselectable">
@ -277,7 +297,7 @@ const showImport = useStorage('showImport', false);
const import_string = ref(''); const import_string = ref('');
const checkInfo = async () => { const checkInfo = async () => {
const required = ['name', 'request.url', 'request.method', 'request.type']; const required = ['name', 'request.url', 'request.method', 'request.type', 'request.data_key'];
for (const key of required) { for (const key of required) {
if (key.includes('.')) { if (key.includes('.')) {
const [parent, child] = key.split('.'); const [parent, child] = key.split('.');
@ -356,6 +376,10 @@ const importItem = async () => {
form.request = item.request form.request = item.request
} }
if (item.data_key) {
form.data_key = item.data_key
}
if (item.on) { if (item.on) {
form.on = item.on form.on = item.on
} }
@ -366,5 +390,4 @@ const importItem = async () => {
toast.error(`Failed to import task. ${e.message}`) toast.error(`Failed to import task. ${e.message}`)
} }
} }
</script> </script>

View file

@ -301,6 +301,10 @@ const setStatus = item => {
return 'Live Streaming'; return 'Live Streaming';
} }
if ('preparing' === item.status) {
return ag(item, 'extras.external_downloader') ? 'External DL' : 'Preparing..';
}
if (!item.status) { if (!item.status) {
return 'Unknown..'; return 'Unknown..';
} }
@ -372,7 +376,7 @@ const updateProgress = (item) => {
} }
if ('preparing' === item.status) { if ('preparing' === item.status) {
return 'Preparing'; return ag(item, 'extras.external_downloader') ? 'External downloader.' : 'Preparing';
} }
if (null != item.status) { if (null != item.status) {

View file

@ -205,6 +205,10 @@ onMounted(async () => {
watch(selectedTheme, value => { watch(selectedTheme, value => {
try { try {
if ('auto' === value) {
applyPreferredColorScheme(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
return
}
applyPreferredColorScheme(value) applyPreferredColorScheme(value)
} catch (e) { } } catch (e) { }
}) })

View file

@ -126,8 +126,8 @@ div.is-centered {
</li> </li>
<li> <li>
When you set the request type as <code>Form</code>, the event data will be JSON encoded and sent as the When you set the request type as <code>Form</code>, the event data will be JSON encoded and sent as the
and sent as <code>...&data=json_string</code>, only the <code>data</code> field will be JSON encoded. The and sent as <code>...&data_key=json_string</code>, only the <code>data</code> field will be JSON encoded.
other keys <code>id</code>, <code>event</code> and <code>created_at</code> will be sent as they are. The other keys <code>id</code>, <code>event</code> and <code>created_at</code> will be sent as they are.
</li> </li>
<li>We also send two special headers <code>X-Event-ID</code> and <code>X-Event</code> with the request.</li> <li>We also send two special headers <code>X-Event-ID</code> and <code>X-Event</code> with the request.</li>
</ul> </ul>