diff --git a/api/settings.js b/api/settings.js index d2cdf76..860ceb9 100644 --- a/api/settings.js +++ b/api/settings.js @@ -37,9 +37,8 @@ module.exports = [ if (isNaN(provided)) data[key] = null else data[key] = +provided } else if (setting.type === "boolean") { - if (provided === "true") data[key] = true - else if (provided === "false") data[key] = false - else data[key] = null + if (provided === "1") data[key] = 1 + else data[key] = 0 } else { throw new Error("Unsupported setting type: "+setting.type) } diff --git a/api/video.js b/api/video.js index 2a6e0dd..4748fc1 100644 --- a/api/video.js +++ b/api/video.js @@ -35,41 +35,34 @@ function formatOrder(format) { return -total } -module.exports = [ - { - route: "/watch", methods: ["GET"], code: async ({req, url}) => { - const id = url.searchParams.get("v") - const user = getUser(req) - const settings = user.getSettingsOrDefaults() - const instanceOrigin = settings.instance - const outURL = `${instanceOrigin}/api/v1/videos/${id}` - try { - const video = await fetch(outURL).then(res => res.json()) - if (!video) throw new Error("The instance returned null.") - if (video.error) throw new InstanceError(video.error, video.identifier) - // video data additional processing - for (const format of video.formatStreams.concat(video.adaptiveFormats)) { - if (!format.second__height && format.resolution) format.second__height = +format.resolution.slice(0, -1) - if (!format.second__order) format.second__order = formatOrder(format) - } - for (const rec of video.recommendedVideos) { - if (!rec.second__lengthText && rec.lengthSeconds > 0) { - rec.second__lengthText = converters.lengthSecondsToLengthText(rec.lengthSeconds) - } - } - const subscribed = user.isSubscribed(video.authorId) - return render(200, "pug/video.pug", {video, subscribed, instanceOrigin}) - } catch (e) { - let message = pug.render("pre= error", {error: e.stack || e.toString()}) - if (e instanceof fetch.FetchError) { - const template = ` +async function renderVideo(videoPromise, {user, id, instanceOrigin}) { + try { + const video = await videoPromise + if (!video) throw new Error("The instance returned null.") + if (video.error) throw new InstanceError(video.error, video.identifier) + // video data additional processing + for (const format of video.formatStreams.concat(video.adaptiveFormats)) { + if (!format.second__height && format.resolution) format.second__height = +format.resolution.slice(0, -1) + if (!format.second__order) format.second__order = formatOrder(format) + } + for (const rec of video.recommendedVideos) { + if (!rec.second__lengthText && rec.lengthSeconds > 0) { + rec.second__lengthText = converters.lengthSecondsToLengthText(rec.lengthSeconds) + } + } + const subscribed = user.isSubscribed(video.authorId) + return render(200, "pug/video.pug", {video, subscribed, instanceOrigin}) + } catch (e) { + let message = pug.render("pre= error", {error: e.stack || e.toString()}) + if (e instanceof fetch.FetchError) { + const template = ` p The selected instance, #[code= instanceOrigin], did not respond correctly. p Requested URL: #[a(href=url)= url] ` - message = pug.render(template, {instanceOrigin, url: outURL}) - } else if (e instanceof InstanceError) { - if (e.identifier === "RATE_LIMITED_BY_YOUTUBE") { - const template = ` + message = pug.render(template, {instanceOrigin, url: outURL}) + } else if (e instanceof InstanceError) { + if (e.identifier === "RATE_LIMITED_BY_YOUTUBE") { + const template = ` .blocked-explanation img(src="/static/images/instance-blocked.svg" width=552 height=96) .rows @@ -96,18 +89,40 @@ p. p. This situation #[em will] be improved in the future! ` - message = pug.render(template, {instanceOrigin}) - } else { - const template = ` + message = pug.render(template, {instanceOrigin}) + } else { + const template = ` p #[strong= error.message] if error.identifier p #[code= error.identifier] p That error was generated by #[code= instanceOrigin]. ` - message = pug.render(template, {instanceOrigin, error: e}) - } + message = pug.render(template, {instanceOrigin, error: e}) + } + } + return render(500, "pug/video.pug", {video: {videoId: id}, error: true, message}) + } +} + +module.exports = [ + { + route: "/watch", methods: ["GET", "POST"], upload: true, code: async ({req, url, body}) => { + const user = getUser(req) + const settings = user.getSettingsOrDefaults() + if (req.method === "GET") { + const id = url.searchParams.get("v") + if (!settings.local) { + const instanceOrigin = settings.instance + const outURL = `${instanceOrigin}/api/v1/videos/${id}` + const videoPromise = fetch(outURL).then(res => res.json()) + return renderVideo(videoPromise, {user, id, instanceOrigin}) + } else { + return render(200, "pug/local-video.pug", {id}) } - return render(500, "pug/video.pug", {video: {videoId: id}, error: true, message}) + } else { // req.method === "POST" + const video = JSON.parse(new URLSearchParams(body.toString()).get("video")) + const videoPromise = Promise.resolve(video) + return renderVideo(videoPromise, {user}) } } } diff --git a/html/static/js/local-video.js b/html/static/js/local-video.js new file mode 100644 index 0000000..7948d4a --- /dev/null +++ b/html/static/js/local-video.js @@ -0,0 +1,18 @@ +import {q} from "./elemjs/elemjs.js" + +const status = q("#status") +const form = q("#form") +const data = q("#video-data") + +fetch(`http://localhost:3000/api/v1/videos/${id}`).then(res => res.json()).then(root => { + if (root.error) throw new Error(root) + data.value = JSON.stringify(root) + form.submit() + status.textContent = "Submitting..." +}).catch(e => { + if (e.message.includes("NetworkError")) { + status.textContent = "Connection failed. Make sure you're running your own instance locally." + } else { + status.innerText = e.toString() + } +}) diff --git a/pug/local-video.pug b/pug/local-video.pug new file mode 100644 index 0000000..64f9c99 --- /dev/null +++ b/pug/local-video.pug @@ -0,0 +1,16 @@ +extends includes/layout + +include includes/video-list-item +include includes/subscribe-button + +block head + title Fetching... - CloudTube + script(type="module" src=getStaticURL("html", "/static/js/local-video.js")) + script const id = !{JSON.stringify(id)} + +block content + main.video-error-page + h2 Fetching video + p#status Waiting... + form(method="post")#form.local-video-form + input(type="hidden" name="video")#video-data diff --git a/pug/settings.pug b/pug/settings.pug index 0f1bbaa..1f09ca4 100644 --- a/pug/settings.pug +++ b/pug/settings.pug @@ -20,7 +20,7 @@ mixin select(id, description, disabled, options) label.description(for=id)= description select(id=id name=id disabled=disabled).border-look each option in options - option(value=option.value selected=(option.value === settings[id]))= option.text + option(value=option.value selected=(option.value == settings[id]))= option.text block head title Settings - CloudTube @@ -38,9 +38,14 @@ block content ]) +select("save_history", "Watch history", false, [ - {value: "", text: "Don't save"}, - {value: "yes", text: "Save"} + {value: "0", text: "Don't save"}, + {value: "1", text: "Save"} + ]) + + +select("local", "Fetch videos", false, [ + {value: "0", text: "Remote instance"}, + {value: "1", text: "Locally"} ]) .save-settings - button.border-look Save \ No newline at end of file + button.border-look Save diff --git a/utils/constants.js b/utils/constants.js index 4625208..fae1253 100644 --- a/utils/constants.js +++ b/utils/constants.js @@ -7,6 +7,10 @@ const constants = { save_history: { type: "boolean", default: false + }, + local: { + type: "boolean", + default: false } }, diff --git a/utils/upgradedb.js b/utils/upgradedb.js index c3f38b2..6438831 100644 --- a/utils/upgradedb.js +++ b/utils/upgradedb.js @@ -23,6 +23,11 @@ const deltas = [ function() { db.prepare("ALTER TABLE Channels ADD COLUMN refreshed INTEGER") .run() + }, + // 2: Settings +local + function() { + db.prepare("ALTER TABLE Settings ADD COLUMN local INTEGER DEFAULT 0") + .run() } ]