mirror of
https://git.sr.ht/~cadence/cloudtube
synced 2024-11-10 02:27:29 +00:00
e0bc0d2e81
Watched videos on your subscriptions feed will be darkened, and the option to hide all of them has been added. This only takes effect if you have enabled saving watched videos on the server in the settings menu - default is off.
143 lines
4.6 KiB
JavaScript
143 lines
4.6 KiB
JavaScript
const fetch = require("node-fetch")
|
|
const {render} = require("pinski/plugins")
|
|
const db = require("../utils/db")
|
|
const {getToken, getUser} = require("../utils/getuser")
|
|
const pug = require("pug")
|
|
const converters = require("../utils/converters")
|
|
|
|
class InstanceError extends Error {
|
|
constructor(error, identifier) {
|
|
super(error)
|
|
this.identifier = identifier
|
|
}
|
|
}
|
|
|
|
function formatOrder(format) {
|
|
// most significant to least significant
|
|
// key, max, order, transform
|
|
// asc: lower number comes first, desc: higher number comes first
|
|
const spec = [
|
|
["second__height", 8000, "desc", x => x ? Math.floor(x/96) : 0],
|
|
["fps", 100, "desc", x => x ? Math.floor(x/10) : 0],
|
|
["type", " ".repeat(60), "asc", x => x.length],
|
|
]
|
|
let total = 0
|
|
for (let i = 0; i < spec.length; i++) {
|
|
const s = spec[i]
|
|
let diff = s[3](format[s[0]])
|
|
if (s[2] === "asc") diff = s[3](s[1]) - diff
|
|
total += diff
|
|
if (i+1 < spec.length) {
|
|
s2 = spec[i+1]
|
|
total *= s2[3](s2[1])
|
|
}
|
|
}
|
|
return -total
|
|
}
|
|
|
|
async function renderVideo(videoPromise, {user, id, instanceOrigin}) {
|
|
try {
|
|
// resolve video
|
|
const video = await videoPromise
|
|
if (!video) throw new Error("The instance returned null.")
|
|
if (video.error) throw new InstanceError(video.error, video.identifier)
|
|
// process stream list ordering
|
|
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)
|
|
}
|
|
// process length text
|
|
for (const rec of video.recommendedVideos) {
|
|
if (!rec.second__lengthText && rec.lengthSeconds > 0) {
|
|
rec.second__lengthText = converters.lengthSecondsToLengthText(rec.lengthSeconds)
|
|
}
|
|
}
|
|
// get subscription data
|
|
const subscribed = user.isSubscribed(video.authorId)
|
|
// process watched videos
|
|
user.addWatchedVideoMaybe(video.videoId)
|
|
const watchedVideos = user.getWatchedVideos()
|
|
if (watchedVideos.length) {
|
|
for (const rec of video.recommendedVideos) {
|
|
rec.watched = watchedVideos.includes(rec.videoId)
|
|
}
|
|
}
|
|
return render(200, "pug/video.pug", {video, subscribed, instanceOrigin})
|
|
} catch (e) {
|
|
// show an appropriate error message
|
|
// these should probably be split out to their own files
|
|
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 = `
|
|
.blocked-explanation
|
|
img(src="/static/images/instance-blocked.svg" width=552 height=96)
|
|
.rows
|
|
.row
|
|
h3.actor You
|
|
| Working
|
|
.row
|
|
h3.actor CloudTube
|
|
| Working
|
|
.row
|
|
h3.actor Instance
|
|
| Blocked by YouTube
|
|
.row
|
|
h3.actor YouTube
|
|
| Working
|
|
p.
|
|
CloudTube needs to a working Second/Invidious instance in order to get data about videos.
|
|
However, the selected instance, #[code= instanceOrigin], has been temporarily blocked by YouTube.
|
|
p.
|
|
You will be able to watch this video if you select a working instance in settings.
|
|
#[br]#[a(href="/settings") Go to settings →]
|
|
p.
|
|
(Tip: Try #[code https://invidious.snopyta.org] or #[code https://invidious.site].)
|
|
p.
|
|
This situation #[em will] be improved in the future!
|
|
`
|
|
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})
|
|
}
|
|
}
|
|
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})
|
|
}
|
|
} else { // req.method === "POST"
|
|
const video = JSON.parse(new URLSearchParams(body.toString()).get("video"))
|
|
const videoPromise = Promise.resolve(video)
|
|
return renderVideo(videoPromise, {user})
|
|
}
|
|
}
|
|
}
|
|
]
|