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():
for key in item.extras.copy():
if not str(key).startswith("playlist"):
item.extras.pop(key, None)
if not self.keep_extra_key(key):
item.extras.pop(key)
if not entry:
return {"status": "error", "msg": "Invalid/empty data was given."}
@ -395,6 +395,10 @@ class DownloadQueue(metaclass=Singleton):
.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)
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."
@ -715,3 +719,17 @@ class DownloadQueue(metaclass=Singleton):
return False, None
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")
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
if item.get("folder") and isinstance(item.get("folder"), str):

View file

@ -46,12 +46,14 @@ class TargetRequest:
method: str
url: str
headers: list[TargetRequestHeader] = field(default_factory=list)
data_key: str = "data"
def serialize(self) -> dict:
return {
"type": self.type,
"method": self.method,
"url": self.url,
"data_key": self.data_key,
"headers": [h.serialize() for h in self.headers],
}
@ -213,7 +215,8 @@ class Notification(metaclass=Singleton):
try:
Notification.validate(target)
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
target = self.make_target(target)
@ -247,6 +250,7 @@ class Notification(metaclass=Singleton):
type=target.get("request", {}).get("type", "json"),
method=target.get("request", {}).get("method", "POST"),
url=target.get("request", {}).get("url"),
data_key=target.get("request", {}).get("data_key", "data"),
headers=[
TargetRequestHeader(
key=str(h.get("key", "")).strip(),
@ -288,6 +292,9 @@ class Notification(metaclass=Singleton):
msg = "Invalid notification target. No URL found."
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"]:
msg = "Invalid notification target. Invalid method found."
raise ValueError(msg)
@ -363,10 +370,17 @@ class Notification(metaclass=Singleton):
for h in target.request.headers:
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():
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)

View file

@ -266,6 +266,19 @@ class Presets(metaclass=Singleton):
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:
"""
Get the preset by id or name.

View file

@ -138,7 +138,7 @@
</div>
</div>
<div class="column is-12 is-clearfix">
<div class="column is-6-tablet is-12-mobile">
<div class="field">
<label class="label is-inline" for="on">
Select Events
@ -167,6 +167,26 @@
</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="field">
<label class="label is-inline is-unselectable">
@ -277,7 +297,7 @@ const showImport = useStorage('showImport', false);
const import_string = ref('');
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) {
if (key.includes('.')) {
const [parent, child] = key.split('.');
@ -356,6 +376,10 @@ const importItem = async () => {
form.request = item.request
}
if (item.data_key) {
form.data_key = item.data_key
}
if (item.on) {
form.on = item.on
}
@ -366,5 +390,4 @@ const importItem = async () => {
toast.error(`Failed to import task. ${e.message}`)
}
}
</script>

View file

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

View file

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

View file

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