simplify API calls.
This commit is contained in:
parent
e5f2a09cc7
commit
922d194d7a
17 changed files with 66 additions and 72 deletions
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
|
|
@ -18,6 +18,7 @@
|
|||
"type": "node",
|
||||
"cwd": "${workspaceFolder}/ui",
|
||||
"env": {
|
||||
"NUXT_API_URL": "http://localhost:8081/api/",
|
||||
"NUXT_PUBLIC_WSS": ":8081/",
|
||||
},
|
||||
"console": "internalConsole"
|
||||
|
|
@ -34,13 +35,13 @@
|
|||
"YTP_DOWNLOAD_PATH": "${workspaceFolder}/var/downloads",
|
||||
"YTP_TEMP_PATH": "${workspaceFolder}/var/tmp",
|
||||
"PYDEVD_DISABLE_FILE_VALIDATION": "1",
|
||||
"YTP_URL_HOST": "http://localhost:8081",
|
||||
"YTP_MAX_WORKERS": "2",
|
||||
"YTP_IGNORE_UI": "true",
|
||||
"YTP_PIP_IGNORE_UPDATES": "true",
|
||||
"YTP_LOG_LEVEL": "DEBUG",
|
||||
"YTP_DEBUG": "true",
|
||||
"YTP_YTDL_DEBUG": "false",
|
||||
"YTP_ACCESS_LOG": "true",
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -54,7 +55,6 @@
|
|||
"YTP_CONFIG_PATH": "${workspaceFolder}/var/config",
|
||||
"YTP_DOWNLOAD_PATH": "${workspaceFolder}/var/downloads",
|
||||
"YTP_TEMP_PATH": "${workspaceFolder}/var/tmp",
|
||||
"YTP_URL_HOST": "http://localhost:8081",
|
||||
"YTP_LOG_LEVEL": "DEBUG",
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -70,7 +70,6 @@ Certain values can be set via environment variables, using the `-e` parameter on
|
|||
* __YTP_DOWNLOAD_PATH__: path to where the downloads will be saved. Defaults to `/downloads` in the docker image, and `./var/downloads` otherwise.
|
||||
* __YTP_TEMP_PATH__: path where intermediary download files will be saved. Defaults to `/tmp` in the docker image, and `./var/tmp` otherwise.
|
||||
* __YTP_TEMP_KEEP__: Whether to keep the Individual video temp directory or remove it. Defaults to `false`.
|
||||
* __YTP_URL_PREFIX__: base path for the web server (for use when hosting behind a reverse proxy). Defaults to `/`.
|
||||
* __YTP_OUTPUT_TEMPLATE__: the template for the filenames of the downloaded videos, formatted according to [this spec](https://github.com/yt-dlp/yt-dlp/blob/master/README.md#output-template). Defaults to `%(title)s.%(ext)s`. This will be the default for all downloads unless the request include output template.
|
||||
* __YTP_OUTPUT_TEMPLATE_CHAPTER__: the template for the filenames of the downloaded videos, when split into chapters via postprocessors, formatted according to [this spec](https://github.com/yt-dlp/yt-dlp/blob/master/README.md#output-template). Defaults to `%(title)s - %(section_number)s %(section_title)s.%(ext)s.`
|
||||
* __YTP_KEEP_ARCHIVE__: Whether to keep history of downloaded videos to prevent downloading same file multiple times. Defaults to `true`.
|
||||
|
|
@ -102,8 +101,6 @@ Certain values can be set via environment variables, using the `-e` parameter on
|
|||
|
||||
It's advisable to run YTPTube behind a reverse proxy, if authentication and/or HTTPS support are required.
|
||||
|
||||
When running behind a reverse proxy which remaps the URL (i.e. serves YTPTube under a subdirectory and not under root), don't forget to set the `YTP_URL_PREFIX` environment variable to the correct value.
|
||||
|
||||
### NGINX
|
||||
|
||||
```nginx
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ class HttpAPI(common):
|
|||
continue
|
||||
|
||||
file = os.path.join(root, file)
|
||||
urlPath = f"{self.config.url_prefix}{file.replace(f'{staticDir}/', '')}"
|
||||
urlPath = f"/{file.replace(f'{staticDir}/', '')}"
|
||||
|
||||
content = open(file, "rb").read()
|
||||
contentType = self.extToMime.get(os.path.splitext(file)[1], MIME.from_file(file))
|
||||
|
|
@ -149,10 +149,10 @@ class HttpAPI(common):
|
|||
method = getattr(self, attr_name)
|
||||
if hasattr(method, "_http_method") and hasattr(method, "_http_path"):
|
||||
http_path = method._http_path
|
||||
if http_path.startswith("/") and self.config.url_prefix.endswith("/"):
|
||||
if http_path.startswith("/"):
|
||||
http_path = method._http_path[1:]
|
||||
|
||||
self.routes.route(method._http_method, self.config.url_prefix + http_path)(method)
|
||||
self.routes.route(method._http_method, f"/{http_path}")(method)
|
||||
|
||||
async def on_prepare(request: Request, response: Response):
|
||||
if "Server" in response.headers:
|
||||
|
|
@ -160,14 +160,10 @@ class HttpAPI(common):
|
|||
|
||||
if "Origin" in request.headers:
|
||||
response.headers["Access-Control-Allow-Origin"] = request.headers["Origin"]
|
||||
response.headers["Access-Control-Allow-Headers"] = "Content-Type"
|
||||
response.headers["Access-Control-Allow-Methods"] = "PATCH, PUT, POST, DELETE"
|
||||
response.headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization"
|
||||
response.headers["Access-Control-Allow-Methods"] = "GET, PATCH, PUT, POST, DELETE"
|
||||
|
||||
if self.config.url_prefix != "/":
|
||||
self.routes.route("GET", "/")(lambda _: web.HTTPFound(self.config.url_prefix))
|
||||
self.routes.get(self.config.url_prefix[:-1])(lambda _: web.HTTPFound(self.config.url_prefix))
|
||||
|
||||
self.routes.static(f"{self.config.url_prefix}api/download/", self.config.download_path)
|
||||
self.routes.static("/api/download/", self.config.download_path)
|
||||
self.preloadStatic(app)
|
||||
|
||||
try:
|
||||
|
|
@ -215,6 +211,10 @@ class HttpAPI(common):
|
|||
|
||||
return middleware_handler
|
||||
|
||||
@route("OPTIONS", "/{path:.*}")
|
||||
async def add_coors(self, _: Request) -> Response:
|
||||
return web.json_response(data={"status": "ok"}, status=web.HTTPOk.status_code)
|
||||
|
||||
@route("GET", "api/ping")
|
||||
async def ping(self, _) -> Response:
|
||||
await self.queue.test()
|
||||
|
|
@ -564,9 +564,7 @@ class HttpAPI(common):
|
|||
raise web.HTTPBadRequest(text="file is required.")
|
||||
|
||||
try:
|
||||
text = await Playlist(url=f"{self.config.url_host}{self.config.url_prefix}").make(
|
||||
download_path=self.config.download_path, file=file
|
||||
)
|
||||
text = await Playlist(url="/").make(download_path=self.config.download_path, file=file)
|
||||
if isinstance(text, Response):
|
||||
return text
|
||||
except StreamingError as e:
|
||||
|
|
@ -604,7 +602,7 @@ class HttpAPI(common):
|
|||
duration = float(duration)
|
||||
|
||||
try:
|
||||
cls = M3u8(f"{self.config.url_host}{self.config.url_prefix}")
|
||||
cls = M3u8("/")
|
||||
if "subtitle" in mode:
|
||||
text = await cls.make_subtitle(self.config.download_path, file, duration)
|
||||
else:
|
||||
|
|
@ -707,22 +705,6 @@ class HttpAPI(common):
|
|||
status=web.HTTPOk.status_code,
|
||||
)
|
||||
|
||||
@route("OPTIONS", "api/add")
|
||||
async def add_cors(self, _: Request) -> Response:
|
||||
return web.json_response(data={"status": "ok"}, status=web.HTTPOk.status_code)
|
||||
|
||||
@route("OPTIONS", "api/tasks")
|
||||
async def cors_add_tasks(self, _: Request) -> Response:
|
||||
return web.json_response(data={"status": "ok"}, status=web.HTTPOk.status_code)
|
||||
|
||||
@route("OPTIONS", "api/yt-dlp/convert")
|
||||
async def cors_ytdlp_convert(self, _: Request) -> Response:
|
||||
return web.json_response(data={"status": "ok"}, status=web.HTTPOk.status_code)
|
||||
|
||||
@route("OPTIONS", "api/delete")
|
||||
async def delete_cors(self, _: Request) -> Response:
|
||||
return web.json_response(data={"status": "ok"}, status=web.HTTPOk.status_code)
|
||||
|
||||
@route("GET", "/")
|
||||
async def index(self, _) -> Response:
|
||||
if "/index.html" not in self.staticHolder:
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ class HttpSocket(common):
|
|||
return wrapper
|
||||
|
||||
def attach(self, app: web.Application):
|
||||
self.sio.attach(app, socketio_path=self.config.url_prefix + "socket.io")
|
||||
self.sio.attach(app, socketio_path=self.config.url_socketio)
|
||||
|
||||
for attr_name in dir(self):
|
||||
method = getattr(self, attr_name)
|
||||
|
|
|
|||
|
|
@ -29,11 +29,7 @@ class Config:
|
|||
temp_keep: bool = False
|
||||
"""Keep temporary files after the download is complete."""
|
||||
|
||||
url_host: str = ""
|
||||
"""The host to bind the server to."""
|
||||
url_prefix: str = ""
|
||||
"""The prefix to use for the server URL."""
|
||||
url_socketio: str = "{url_prefix}socket.io"
|
||||
url_socketio: str = "/socket.io"
|
||||
"""The URL to use for the socket.io server."""
|
||||
|
||||
output_template: str = "%(title)s.%(ext)s"
|
||||
|
|
@ -192,9 +188,7 @@ class Config:
|
|||
"output_template",
|
||||
"ytdlp_version",
|
||||
"version",
|
||||
"url_host",
|
||||
"started",
|
||||
"url_prefix",
|
||||
"remove_files",
|
||||
"ui_update_title",
|
||||
"max_workers",
|
||||
|
|
@ -275,9 +269,6 @@ class Config:
|
|||
if k in self._int_vars:
|
||||
setattr(self, k, int(v))
|
||||
|
||||
if not self.url_prefix.endswith("/"):
|
||||
self.url_prefix += "/"
|
||||
|
||||
numeric_level = getattr(logging, self.log_level.upper(), None)
|
||||
if not isinstance(numeric_level, int):
|
||||
raise ValueError(f"Invalid log level '{self.log_level}' specified.")
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { request } from '~/utils/index'
|
||||
|
||||
const props = defineProps({
|
||||
image: {
|
||||
type: String,
|
||||
|
|
@ -38,7 +40,6 @@ const props = defineProps({
|
|||
|
||||
const cache = useSessionCache()
|
||||
const toast = useToast()
|
||||
const config = useConfigStore()
|
||||
const url = ref()
|
||||
const error = ref(false)
|
||||
const isPreloading = ref(false)
|
||||
|
|
@ -53,8 +54,8 @@ const defaultLoader = async () => {
|
|||
return
|
||||
}
|
||||
|
||||
const response = await fetch(config.app.url_host + config.app.url_prefix + 'api/thumbnail?url=' + encodePath(props.image), {
|
||||
signal: cancelRequest.signal
|
||||
const response = await request('/api/thumbnail?url=' + encodePath(props.image), {
|
||||
signal: cancelRequest.signal,
|
||||
})
|
||||
|
||||
if (200 !== response.status) {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
const config = useConfigStore()
|
||||
import { request } from '~/utils/index'
|
||||
|
||||
const emitter = defineEmits(['closeModel'])
|
||||
const isLoading = ref(false)
|
||||
const data = ref({})
|
||||
|
|
@ -46,10 +47,10 @@ const eventFunc = e => {
|
|||
|
||||
onMounted(async () => {
|
||||
window.addEventListener('keydown', eventFunc)
|
||||
const url = config.app.url_host + config.app.url_prefix + 'api/url/info?url=' + encodePath(props.link)
|
||||
const url = '/api/url/info?url=' + encodePath(props.link)
|
||||
try {
|
||||
isLoading.value = true
|
||||
const response = await fetch(url);
|
||||
const response = await request(url, { credentials: 'include' });
|
||||
data.value = await response.json();
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
|
|
|
|||
|
|
@ -99,14 +99,11 @@
|
|||
<figure class="image is-3by1">
|
||||
<span v-if="'finished' === item.status" @click="playVideo(item)" class="play-overlay">
|
||||
<div class="play-icon"></div>
|
||||
<img
|
||||
:src="config.app.url_host + config.app.url_prefix + 'api/thumbnail?url=' + encodePath(item.extras.thumbnail)"
|
||||
v-if="item.extras?.thumbnail" />
|
||||
<img :src="'/api/thumbnail?url=' + encodePath(item.extras.thumbnail)" v-if="item.extras?.thumbnail" />
|
||||
<img v-else src="/images/placeholder.png" />
|
||||
</span>
|
||||
<template v-else>
|
||||
<img v-if="item.extras?.thumbnail"
|
||||
:src="config.app.url_host + config.app.url_prefix + 'api/thumbnail?url=' + encodePath(item.extras.thumbnail)" />
|
||||
<img v-if="item.extras?.thumbnail" :src="'/api/thumbnail?url=' + encodePath(item.extras.thumbnail)" />
|
||||
<img v-else src="/images/placeholder.png" />
|
||||
</template>
|
||||
</figure>
|
||||
|
|
@ -276,7 +273,7 @@ const playVideo = item => {
|
|||
video_link.value = makeDownload(config, item, 'm3u8')
|
||||
video_title.value = item.title
|
||||
if (item.extras?.thumbnail) {
|
||||
video_thumbnail.value = config.app.url_host + config.app.url_prefix + 'api/thumbnail?url=' + encodePath(item.extras.thumbnail)
|
||||
video_thumbnail.value = '/api/thumbnail?url=' + encodePath(item.extras.thumbnail)
|
||||
}
|
||||
if (item.extras?.channel) {
|
||||
video_artist.value = item.extras.channel
|
||||
|
|
|
|||
|
|
@ -137,6 +137,7 @@
|
|||
|
||||
<script setup>
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { request } from '~/utils/index'
|
||||
|
||||
const emitter = defineEmits(['getInfo'])
|
||||
const config = useConfigStore()
|
||||
|
|
@ -155,7 +156,7 @@ const addInProgress = ref(false)
|
|||
const addDownload = async () => {
|
||||
// -- send request to convert cli options to JSON
|
||||
if (ytdlpConfig.value && ytdlpConfig.value.length > 2 && !ytdlpConfig.value.trim().startsWith('{')) {
|
||||
const response = await fetch(config.app.url_host + config.app.url_prefix + 'api/yt-dlp/convert', {
|
||||
const response = await request('/api/yt-dlp/convert', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
|
|
|||
|
|
@ -54,10 +54,10 @@
|
|||
<div v-if="false === hideThumbnail" class="card-image">
|
||||
<figure class="image is-3by1" v-if="item.extras?.thumbnail">
|
||||
<img :alt="item.title"
|
||||
:src="config.app.url_host + config.app.url_prefix + 'api/thumbnail?url=' + encodePath(item.extras.thumbnail)" />
|
||||
:src="'/api/thumbnail?url=' + encodePath(item.extras.thumbnail)" />
|
||||
</figure>
|
||||
<figure class="image is-3by1" v-else>
|
||||
<img :src="config.app.url_host + config.app.url_prefix + 'images/placeholder.png'" />
|
||||
<img :src="'/images/placeholder.png'" />
|
||||
</figure>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
|
|
|
|||
|
|
@ -190,6 +190,7 @@
|
|||
|
||||
<script setup>
|
||||
import { parseExpression } from 'cron-parser'
|
||||
import { request } from '~/utils/index'
|
||||
|
||||
const emitter = defineEmits(['cancel', 'submit']);
|
||||
const toast = useToast();
|
||||
|
|
@ -242,7 +243,7 @@ const checkInfo = async () => {
|
|||
|
||||
// -- send request to convert cli options to JSON
|
||||
if (form.ytdlp_config && form.ytdlp_config.length > 2 && !form.ytdlp_config.trim().startsWith('{')) {
|
||||
const response = await fetch(config.app.url_host + config.app.url_prefix + 'api/yt-dlp/convert', {
|
||||
const response = await request('/api/yt-dlp/convert', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ import 'assets/css/style.css'
|
|||
import 'assets/css/all.css'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import moment from "moment";
|
||||
import { request } from '~/utils/index'
|
||||
|
||||
const Year = new Date().getFullYear()
|
||||
const selectedTheme = useStorage('theme', (() => window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')())
|
||||
|
|
@ -159,7 +160,7 @@ const checkCookies = async () => {
|
|||
|
||||
try {
|
||||
isChecking.value = true
|
||||
const response = await fetch(config.app.url_host + config.app.url_prefix + 'api/youtube/auth')
|
||||
const response = await request('/api/youtube/auth')
|
||||
const data = await response.json()
|
||||
if (response.ok) {
|
||||
toast.success('Succuss. ' + data.message)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,22 @@
|
|||
import path from "path";
|
||||
|
||||
let extraNitro = {}
|
||||
try {
|
||||
const API_URL = import.meta.env.NUXT_API_URL;
|
||||
if (API_URL) {
|
||||
extraNitro = {
|
||||
devProxy: {
|
||||
'/api/': {
|
||||
target: API_URL,
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
|
||||
export default defineNuxtConfig({
|
||||
ssr: false,
|
||||
devtools: { enabled: false },
|
||||
|
|
@ -48,7 +65,8 @@ export default defineNuxtConfig({
|
|||
nitro: {
|
||||
output: {
|
||||
publicDir: path.join(__dirname, 'exported')
|
||||
}
|
||||
},
|
||||
...extraNitro,
|
||||
},
|
||||
|
||||
telemetry: false,
|
||||
|
|
|
|||
|
|
@ -122,6 +122,7 @@ div.is-centered {
|
|||
<script setup>
|
||||
import moment from 'moment'
|
||||
import { parseExpression } from 'cron-parser'
|
||||
import { request } from '~/utils/index'
|
||||
|
||||
const toast = useToast()
|
||||
const config = useConfigStore()
|
||||
|
|
@ -151,7 +152,8 @@ watch(() => socket.isConnected, async () => {
|
|||
const reloadContent = async (fromMounted = false) => {
|
||||
try {
|
||||
isLoading.value = true
|
||||
const response = await fetch(config.app.url_host + config.app.url_prefix + 'api/tasks')
|
||||
const response = await request('/api/tasks')
|
||||
|
||||
if (fromMounted && !response.ok) {
|
||||
return
|
||||
}
|
||||
|
|
@ -182,7 +184,7 @@ const resetTask = (closeForm = false) => {
|
|||
}
|
||||
|
||||
const updateTasks = async tasks => {
|
||||
const response = await fetch(config.app.url_host + config.app.url_prefix + 'api/tasks', {
|
||||
const response = await request('/api/tasks', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@ const CONFIG_KEYS = {
|
|||
ytdlp_version: '',
|
||||
max_workers: 1,
|
||||
version: '',
|
||||
url_host: '',
|
||||
url_prefix: '',
|
||||
has_cookies: false,
|
||||
basic_mode: true,
|
||||
default_preset: 'default',
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export const useSocketStore = defineStore('socket', () => {
|
|||
const isConnected = ref(false);
|
||||
|
||||
const connect = () => {
|
||||
socket.value = io(runtimeConfig.public.wss)
|
||||
socket.value = io(runtimeConfig.public.wss, { withCredentials: true })
|
||||
|
||||
socket.value.on('connect', () => isConnected.value = true);
|
||||
socket.value.on('disconnect', () => isConnected.value = false);
|
||||
|
|
|
|||
|
|
@ -229,7 +229,7 @@ const encodePath = item => {
|
|||
* And prefix the URL with the API URL and path.
|
||||
*
|
||||
* @param {string} url - The URL to request
|
||||
* @param {object} options - The request options
|
||||
* @param {RequestInit} options - The request options
|
||||
*
|
||||
* @returns {Promise<Response>} - The response from the API
|
||||
*/
|
||||
|
|
@ -254,7 +254,11 @@ const request = (url, options = {}) => {
|
|||
options.headers['Accept'] = 'application/json'
|
||||
}
|
||||
|
||||
return fetch(url.startsWith('/') ? runtimeConfig.public.domain + url : url, options)
|
||||
if (url.startsWith('/')) {
|
||||
options.credentials = 'same-origin'
|
||||
}
|
||||
|
||||
return fetch(url.startsWith('/') ? eTrim(runtimeConfig.public.domain, '/') + '/' + sTrim(url, '/') : url, options)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -353,7 +357,7 @@ const makeDownload = (config, item, base = 'api/download') => {
|
|||
baseDir += item.folder + '/';
|
||||
}
|
||||
|
||||
let url = config.app.url_host + encodePath(config.app.url_prefix + baseDir + item.filename);
|
||||
let url = `/${sTrim(baseDir, '/')}${encodePath(item.filename)}`;
|
||||
return ('m3u8' === base) ? url + '.m3u8' : url;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue