diff --git a/README.md b/README.md
index 2ae6bd7d..1b48a99d 100644
--- a/README.md
+++ b/README.md
@@ -77,6 +77,7 @@ Certain configuration values can be set via environment variables, using the `-e
| YTP_MAX_WORKERS | How many works to use for downloads | `1` |
| YTP_AUTH_USERNAME | Username for basic authentication | `empty string` |
| YTP_AUTH_PASSWORD | Password for basic authentication | `empty string` |
+| YTP_CONSOLE_ENABLED | Whether to enable the console | `false` |
| YTP_REMOVE_FILES | Remove the actual file when clicking the remove button | `false` |
| YTP_CONFIG_PATH | Path to where the queue persistence files will be saved | `/config` |
| YTP_TEMP_PATH | Path where intermediary download files will be saved | `/tmp` |
diff --git a/app/library/HttpAPI.py b/app/library/HttpAPI.py
index fb77f979..67535b8c 100644
--- a/app/library/HttpAPI.py
+++ b/app/library/HttpAPI.py
@@ -608,8 +608,21 @@ class HttpAPI(Common):
if not url:
return web.json_response(data={"error": "url param is required."}, status=web.HTTPBadRequest.status_code)
+ data = {
+ "url": url,
+ }
+
+ preset = request.query.get("preset")
+ if preset:
+ exists = Presets.get_instance().get(preset)
+ if not exists:
+ return web.json_response(
+ data={"error": f"Preset '{preset}' does not exist."}, status=web.HTTPBadRequest.status_code
+ )
+ data["preset"] = preset
+
try:
- status = await self.add(**self.format_item({"url": url}))
+ 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)
diff --git a/app/library/HttpSocket.py b/app/library/HttpSocket.py
index 4ba96b6f..30ca13fc 100644
--- a/app/library/HttpSocket.py
+++ b/app/library/HttpSocket.py
@@ -93,6 +93,10 @@ class HttpSocket(Common):
@ws_event
async def cli_post(self, sid: str, data):
+ if not self.config.console_enabled:
+ await self.emitter.error("Console is disabled.", to=sid)
+ return
+
if not data:
await self.emitter.emit(Events.CLI_CLOSE, {"exitcode": 0}, to=sid)
return
diff --git a/app/library/config.py b/app/library/config.py
index a331c1ca..b1a9c2b0 100644
--- a/app/library/config.py
+++ b/app/library/config.py
@@ -143,6 +143,9 @@ class Config:
secret_key: str
"The secret key to use for the application."
+ console_enabled: bool = False
+ "Enable direct access to yt-dlp console."
+
_manual_vars: tuple = (
"temp_path",
"config_path",
@@ -183,6 +186,7 @@ class Config:
"pip_ignore_updates",
"basic_mode",
"file_logging",
+ "console_enabled",
)
"The variables that are booleans."
@@ -200,6 +204,7 @@ class Config:
"default_preset",
"instance_title",
"sentry_dsn",
+ "console_enabled",
)
"The variables that are relevant to the frontend."
@@ -353,14 +358,13 @@ class Config:
# save key as bytes.
if os.path.exists(key_file) and os.path.getsize(key_file) > 5:
- with open(key_file,"rb") as f:
+ with open(key_file, "rb") as f:
self.secret_key = f.read().strip()
else:
self.secret_key = os.urandom(32)
with open(key_file, "wb") as f:
f.write(self.secret_key)
-
self.started = time.time()
def _get_attributes(self) -> dict:
diff --git a/ui/assets/css/style.css b/ui/assets/css/style.css
index f166f27f..58aa328c 100644
--- a/ui/assets/css/style.css
+++ b/ui/assets/css/style.css
@@ -259,3 +259,7 @@ hr {
.is-pre {
white-space: pre;
}
+
+.has-text-bold {
+ font-weight: bold;
+}
diff --git a/ui/components/EmbedPlayer.vue b/ui/components/EmbedPlayer.vue
new file mode 100644
index 00000000..0b692a4e
--- /dev/null
+++ b/ui/components/EmbedPlayer.vue
@@ -0,0 +1,30 @@
+
+ Not downloaded yet.
+
+
pImg(e)" v-if="item.extras?.thumbnail"
:src="'/api/thumbnail?url=' + encodePath(item.extras.thumbnail)" />
@@ -243,6 +249,14 @@
+
+
+
+
+
+
+
- - {{ tryParse(item.timer) }} - {{ item.timer }} - + {{ tryParse(item.timer) }} - {{ item.timer }}
-+
- {{ item.folder ? calcPath(item.folder) : config.app.download_path }} + {{ calcPath(item.folder) }}
-+
- {{ item.template ? item.template : config.app.output_template }} + {{ item.template }}
-+
{{ item.preset ?? config.app.default_preset }}
@@ -286,7 +284,7 @@ const calcPath = path => { } onMounted(async () => { - if (!socket.isConnected){ + if (!socket.isConnected) { return; } socket.on('status', statusHandler) diff --git a/ui/stores/ConfigStore.js b/ui/stores/ConfigStore.js index 2fde0e1d..6fe9523e 100644 --- a/ui/stores/ConfigStore.js +++ b/ui/stores/ConfigStore.js @@ -16,6 +16,7 @@ const CONFIG_KEYS = { default_preset: 'default', instance_title: null, sentry_dsn: null, + console_enabled: false, }, presets: [ { diff --git a/ui/utils/embedable.js b/ui/utils/embedable.js new file mode 100644 index 00000000..be78437c --- /dev/null +++ b/ui/utils/embedable.js @@ -0,0 +1,69 @@ +const sources = [ + { + name: 'youtube', + url: 'https://www.youtube-nocookie.com/embed/', + regex: /(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})/, + }, + { + name: "instagram", + url: "https://www.instagram.com/p/", + regex: /https?:\/\/(?:www\.)?instagram\.com\/p\/([^/?#&]+)/, + }, + { + name: "twitter", + url: "https://twitter.com/i/status/", + regex: /https?:\/\/(?:www\.)?twitter\.com\/i\/status\/([^/?#&]+)/, + }, + { + name: "facebook", + url: "https://www.facebook.com/plugins/post.php?href=", + regex: /https?:\/\/(?:www\.)?facebook\.com\/(?:[^/?#&]+)\/posts\/([^/?#&]+)/, + }, + { + name: "tiktok", + url: "https://www.tiktok.com/embed/", + regex: /https?:\/\/(?:www\.)?tiktok\.com\/(?:[^/?#&]+)\/video\/([^/?#&]+)/, + }, + { + name: "vimeo", + url: "https://player.vimeo.com/video/", + regex: /https?:\/\/(?:www\.)?vimeo\.com\/(?:[^/?#&]+)\/([^/?#&]+)/, + }, + { + name: "soundcloud", + url: "https://w.soundcloud.com/player/?url=", + regex: /https?:\/\/(?:www\.)?soundcloud\.com\/(?:[^/?#&]+)\/([^/?#&]+)/, + }, + { + name: "spotify", + url: "https://open.spotify.com/embed/", + regex: /https?:\/\/(?:open\.)?spotify\.com\/(?:[^/?#&]+)\/([^/?#&]+)/, + }, + { + name: "twitch", + url: "https://player.twitch.tv/?channel=", + regex: /https?:\/\/(?:www\.)?twitch\.tv\/(?:[^/?#&]+)\/([^/?#&]+)/, + }, + { + name: "mixcloud", + url: "https://www.mixcloud.com/widget/iframe/", + regex: /https?:\/\/(?:www\.)?mixcloud\.com\/(?:[^/?#&]+)\/([^/?#&]+)/, + }, + { + name: "dailymotion", + url: "https://www.dailymotion.com/embed/video/", + regex: /https?:\/\/(?:www\.)?dailymotion\.com\/(?:[^/?#&]+)\/video\/([^/?#&]+)/, + }, +] + +const isEmbedable = url => { + return sources.some(source => source.regex.test(url)); +} + +const getEmbedable = url => { + const source = sources.find(source => source.regex.test(url)); + const id = source.regex.exec(url)[1]; + return source.url + id; +} + +export { isEmbedable, getEmbedable };