1
0
Fork 0
mirror of https://git.sr.ht/~cadence/cloudtube synced 2026-03-02 10:41:36 +00:00

Settings page and instance selection

This commit is contained in:
Cadence Ember 2020-09-01 01:22:16 +12:00
parent 59a7489545
commit c573a5ac3e
No known key found for this signature in database
GPG key ID: 128B99B1B74A6412
22 changed files with 587 additions and 71 deletions

View file

@ -7,8 +7,9 @@ module.exports = [
{
route: `/channel/(${constants.regex.ucid})`, methods: ["GET"], code: async ({req, fill}) => {
const id = fill[0]
const data = await fetchChannel(id)
const user = getUser(req)
const settings = user.getSettingsOrDefaults()
const data = await fetchChannel(id, settings.instance)
const subscribed = user.isSubscribed(id)
return render(200, "pug/channel.pug", {data, subscribed})
}

67
api/settings.js Normal file
View file

@ -0,0 +1,67 @@
const {render, redirect} = require("pinski/plugins")
const db = require("./utils/db")
const {getToken, getUser} = require("./utils/getuser")
const constants = require("./utils/constants")
const validate = require("./utils/validate")
const V = validate.V
module.exports = [
{
route: "/settings", methods: ["GET"], code: async ({req}) => {
const user = getUser(req)
const settings = user.getSettings()
return render(200, "pug/settings.pug", {constants, settings})
}
},
{
route: "/settings", methods: ["POST"], upload: true, code: async ({req, body}) => {
return new V()
.with(validate.presetLoad({body}))
.with(validate.presetURLParamsBody())
.last(async state => {
const {params} = state
const responseHeaders = {
Location: "/settings"
}
const token = getToken(req, responseHeaders)
const data = {}
for (const key of Object.keys(constants.user_settings)) {
const setting = constants.user_settings[key]
if (params.has(key)) {
const provided = params.get(key)
if (setting.type === "string") {
if (provided) data[key] = provided
else data[key] = null
} else if (setting.type === "integer") {
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
} else {
throw new Error("Unsupported setting type: "+setting.type)
}
} else {
data[key] = null
}
}
db.prepare("DELETE FROM Settings WHERE token = ?").run(token)
const keys = ["token", ...Object.keys(constants.user_settings)]
const baseFields = keys.join(", ")
const atFields = keys.map(k => "@"+k).join(", ")
db.prepare(`INSERT INTO Settings (${baseFields}) VALUES (${atFields})`).run({token, ...data})
return {
statusCode: 303,
headers: responseHeaders,
contentType: "text/html",
content: "Redirecting..."
}
})
.go()
}
}
]

View file

@ -1,4 +1,15 @@
const constants = {
user_settings: {
instance: {
type: "string",
default: "https://invidious.snopyta.org"
},
save_history: {
type: "boolean",
default: false
}
},
caching: {
csrf_time: 4*60*60*1000
},

View file

@ -23,6 +23,22 @@ class User {
this.token = token
}
getSettings() {
if (this.token) {
return db.prepare("SELECT * FROM Settings WHERE token = ?").get(this.token) || {}
} else {
return {}
}
}
getSettingsOrDefaults() {
const settings = this.getSettings()
for (const key of Object.keys(settings)) {
if (settings[key] === null) settings[key] = constants.user_settings[key].default
}
return settings
}
getSubscriptions() {
if (this.token) {
return db.prepare("SELECT ucid FROM Subscriptions WHERE token = ?").pluck().all(this.token)

View file

@ -10,8 +10,14 @@ const deltas = [
.run()
db.prepare("CREATE TABLE Channels (ucid TEXT NOT NULL, name TEXT NOT NULL, icon_url TEXT, PRIMARY KEY (ucid))")
.run()
db.prepare("CREATE TABLE Videos (videoId TEXT NOT NULL, title TEXT NOT NULL, author TEXT, authorId TEXT NOT NULL, published INTEGER, publishedText TEXT, lengthText TEXT, viewCountText TEXT, descriptionHtml TEXT, PRIMARY KEY (videoId))")
.run()
db.prepare("CREATE TABLE CSRFTokens (token TEXT NOT NULL, expires INTEGER NOT NULL, PRIMARY KEY (token))")
.run()
db.prepare("CREATE TABLE SeenTokens (token TEXT NOT NULL, seen INTEGER NOT NULL, PRIMARY KEY (token))")
.run()
db.prepare("CREATE TABLE Settings (token TEXT NOT NULL, instance TEXT, save_history INTEGER, PRIMARY KEY (token))")
.run()
}
]

View file

@ -1,9 +1,9 @@
const fetch = require("node-fetch")
const db = require("./db")
async function fetchChannel(ucid) {
async function fetchChannel(ucid, instance) {
// fetch
const channel = await fetch(`http://localhost:3000/api/v1/channels/${ucid}`).then(res => res.json())
const channel = await fetch(`${instance}/api/v1/channels/${ucid}`).then(res => res.json())
// update database
const bestIcon = channel.authorThumbnails.slice(-1)[0]
const iconURL = bestIcon ? bestIcon.url : null

View file

@ -1,19 +1,48 @@
const fetch = require("node-fetch")
const {render} = require("pinski/plugins")
const db = require("./utils/db")
const {getToken} = require("./utils/getuser")
const {getToken, getUser} = require("./utils/getuser")
const pug = require("pug")
class InstanceError extends Error {
constructor(error, identifier) {
super(error)
this.identifier = identifier
}
}
module.exports = [
{
route: "/watch", methods: ["GET"], code: async ({req, url}) => {
const id = url.searchParams.get("v")
const video = await fetch(`http://localhost:3000/api/v1/videos/${id}`).then(res => res.json())
let subscribed = false
const token = getToken(req)
if (token) {
subscribed = !!db.prepare("SELECT * FROM Subscriptions WHERE token = ? AND ucid = ?").get([token, video.authorId])
const user = getUser(req)
const settings = user.getSettingsOrDefaults()
const outURL = `${settings.instance}/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)
const subscribed = user.isSubscribed(video.authorId)
return render(200, "pug/video.pug", {video, subscribed})
} 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= instance], did not respond correctly.
p Requested URL: #[a(href=url)= url]
`
message = pug.render(template, {instance: settings.instance, url: outURL})
} else if (e instanceof InstanceError) {
const template = `
p #[strong= error.message]
if error.identifier
p #[code= error.identifier]
p That error was generated by #[code= instance].
`
message = pug.render(template, {instance: settings.instance, error: e})
}
return render(500, "pug/video.pug", {video: {videoId: id}, error: true, message})
}
return render(200, "pug/video.pug", {video, subscribed})
}
}
]