Merge pull request #206 from arabcoders/dev

Improve history status display
This commit is contained in:
Abdulmohsen 2025-03-10 01:48:30 +03:00 committed by GitHub
commit 9427f7b2a0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 81 additions and 50 deletions

14
API.md
View file

@ -171,21 +171,19 @@ or an error:
**Purpose**: **(Quick Add)** Add a single URL to the download queue via GET.
**Query Parameters**:
- `?url=<video-url>`
- `url=<video-url>`
- `preset=<preset-name>`
**Response**:
```json
{
"status": "(ok|error)",
}
```
or an error:
```json
{
"error": "text"
"status": true|false, # true if added to the queue. false otherwise.
"message": "..." # the response message.
}
```
- If `url` is missing, returns `400 Bad Request`.
- If `preset` is set and not found, returns `404 Not Found`.
---

View file

@ -119,8 +119,15 @@ Change the the variable `url` and `preset` variables to match your YTPTube insta
### iOS Shortcuts
not yet available.
You can download [Add To YTPTube](https://www.icloud.com/shortcuts/18b8f70666a04a06aed09424f97ce951) shortcut and use it to send links to your YTPTube instance.
You have to edit the shortcut and replace the following:
- `https://ytp.example.org` with your YTPTube instance.
- `username:password` replace this with your own username & password or leave it empty if you don't have authentication enabled.
This shortcut is powerful, as it's allow you to select your preset on the fly pulled directly from your instance.
Combined with the new and powerful presets system, you could add presets for specific websites that need cookies,
and use that preset to download directly from your iOS device.
## Running behind a reverse proxy

View file

@ -614,19 +614,24 @@ class HttpAPI(Common):
preset = request.query.get("preset")
if preset:
exists = Presets.get_instance().get(preset)
exists = Presets.get_instance().get(name=preset)
if not exists:
return web.json_response(
data={"error": f"Preset '{preset}' does not exist."}, status=web.HTTPBadRequest.status_code
data={"status": False, "message": f"Preset '{preset}' does not exist."},
status=web.HTTPBadRequest.status_code,
)
data["preset"] = preset
try:
status = await self.add(**self.format_item(data))
except ValueError as e:
return web.json_response(data={"error": str(e)}, status=web.HTTPBadRequest.status_code)
return web.json_response(data={"status": False, "message": str(e)}, status=web.HTTPBadRequest.status_code)
return web.json_response(data=status, status=web.HTTPOk.status_code, dumps=self.encoder.encode)
return web.json_response(
data={"status": status.get("status") == "ok", "message": status.get("msg", "URL added")},
status=web.HTTPOk.status_code,
dumps=self.encoder.encode,
)
@route("POST", "api/history")
async def history_item_add(self, request: Request) -> Response:

View file

@ -251,7 +251,7 @@ class Presets(metaclass=Singleton):
try:
with open(self._file, "w") as f:
json.dump(obj=[preset.serialize() for preset in presets], fp=f, indent=4)
json.dump(obj=[preset.serialize() for preset in presets if preset.default is False], fp=f, indent=4)
LOG.info(f"Presets saved to '{self._file}'.")
except Exception as e:

View file

@ -86,6 +86,10 @@
</div>
<div class="card-header-icon">
<a v-if="hideThumbnail && 'finished' === item.status" href="#" @click.prevent="playVideo(item)"
v-tooltip="'Play video.'">
<span class="icon"><i class="fa-solid fa-play" /></span>
</a>
<a :href="item.url" class="has-text-primary" v-tooltip="'Copy url.'" @click.prevent="copyText(item.url)">
<span class="icon"><i class="fa-solid fa-copy" /></span>
</a>
@ -118,11 +122,6 @@
</div>
<div class="card-content">
<div class="columns is-mobile is-multiline">
<div class="column is-12" v-if="item.live_in">
<span class="has-text-info">
Live stream is scheduled to start at {{ moment(item.live_in).format() }}
</span>
</div>
<div class="column is-12" v-if="item.error">
<span class="has-text-danger">{{ item.error }}</span>
</div>
@ -130,23 +129,16 @@
<span class="has-text-danger">{{ item.msg }}</span>
</div>
<div class="column is-half-mobile has-text-centered is-text-overflow">
<span v-if="!item.live_in && !item.is_live">
<span class="icon-text">
<span class="icon"
:class="{ 'has-text-success': item.status === 'finished', 'has-text-danger': item.status !== 'finished' }">
<i :class="setIcon(item)" />
</span>
<span v-if="item.status == 'finished' && item.is_live">Live Ended</span>
<span v-else>{{ ucFirst(item.status) }}</span>
</span>
<span class="icon-text">
<span class="icon" :class="setIconColor(item)"><i :class="setIcon(item)" /></span>
<span>{{ setStatus(item) }}</span>
</span>
<span v-else>
<span class="icon-text">
<span class="icon has-text-info">
<i class="fa-solid fa-calendar" />
</span>
<span>Live</span>
</span>
</div>
<div class="column is-half-mobile has-text-centered is-text-overflow"
v-if="item.live_in && 'not_live' === item.status">
<span :date-datetime="item.live_in" class="user-hint"
v-tooltip="'Starts at: ' + moment(item.live_in).format('YYYY-M-DD H:mm Z')">
{{ moment(item.live_in).fromNow() }}
</span>
</div>
<div class="column is-half-mobile has-text-centered is-text-overflow">
@ -155,13 +147,6 @@
{{ moment(item.datetime).fromNow() }}
</span>
</div>
<div class="column is-half-mobile has-text-centered is-text-overflow"
v-if="item.live_in && item.status != 'finished'">
<span :date-datetime="item.live_in"
v-tooltip="'Starts at: ' + moment(item.live_in).format('YYYY-M-DD H:mm Z')">
{{ moment(item.live_in).fromNow() }}
</span>
</div>
<div class="column is-half-mobile has-text-centered is-text-overflow" v-if="item.file_size">
{{ formatBytes(item.file_size) }}
</div>
@ -400,25 +385,61 @@ const clearIncomplete = () => {
}
const setIcon = item => {
if (item.status === 'finished' && item.is_live) {
return 'fa-solid fa-globe'
if ('finished' === item.status) {
return item.is_live ? 'fa-solid fa-globe' : 'fa-solid fa-circle-check'
}
if (item.status === 'finished') {
return 'fa-solid fa-circle-check'
}
if (item.status === 'error') {
if ('error' === item.status) {
return 'fa-solid fa-circle-xmark'
}
if (item.status === 'cancelled') {
if ('cancelled' === item.status) {
return 'fa-solid fa-eject'
}
if ('not_live' === item.status) {
return 'fa-solid fa-hourglass-half fa-spin'
}
return 'fa-solid fa-circle'
}
const setIconColor = item => {
if (item.status === 'finished') {
return 'has-text-success'
}
if ('not_live' === item.status) {
return 'has-text-info'
}
if ('cancelled' === item.status) {
return 'has-text-warning'
}
return 'has-text-danger'
}
const setStatus = item => {
if ('finished' === item.status) {
return item.is_live ? 'Live Ended' : 'Completed'
}
if ('error' === item.status) {
return 'Error'
}
if ('cancelled' === item.status) {
return 'User Cancelled'
}
if ('not_live' === item.status) {
return 'Live Stream'
}
return item.status
}
const requeueIncomplete = () => {
if (false === confirm('Are you sure you want to re-queue all incomplete downloads?')) {
return false