Compare commits

..

No commits in common. "fork-master" and "rei/hacky_embed" have entirely different histories.

70 changed files with 716 additions and 1602 deletions

View File

@ -1,28 +1,18 @@
const {render} = require("pinski/plugins") const {render} = require("pinski/plugins")
const constants = require("../utils/constants")
const {fetchChannel} = require("../utils/youtube") const {fetchChannel} = require("../utils/youtube")
const {getUser} = require("../utils/getuser") const {getUser} = require("../utils/getuser")
const converters = require("../utils/converters") const converters = require("../utils/converters")
module.exports = [ module.exports = [
{ {
route: `/(c|channel|user)/(.+)`, methods: ["GET"], code: async ({req, fill, url}) => { route: `/channel/(${constants.regex.ucid})`, methods: ["GET"], code: async ({req, fill, url}) => {
const path = fill[0] const id = fill[0]
const id = fill[1]
const user = getUser(req) const user = getUser(req)
const settings = user.getSettingsOrDefaults() const settings = user.getSettingsOrDefaults()
const data = await fetchChannel(path, id, settings.instance) const data = await fetchChannel(id, settings.instance)
const subscribed = user.isSubscribed(id)
const instanceOrigin = settings.instance const instanceOrigin = settings.instance
// problem with the channel? fetchChannel has collected the necessary information for us.
// we can render a skeleton page, display the message, and provide the option to unsubscribe.
if (data.error) {
const statusCode = data.missing ? 410 : 500
const subscribed = user.isSubscribed(id)
return render(statusCode, "pug/channel-error.pug", {req, settings, data, subscribed, instanceOrigin})
}
// everything is fine
// normalise info, apply watched status // normalise info, apply watched status
if (!data.second__subCountText && data.subCount) { if (!data.second__subCountText && data.subCount) {
data.second__subCountText = converters.subscriberCountToText(data.subCount) data.second__subCountText = converters.subscriberCountToText(data.subCount)
@ -34,8 +24,7 @@ module.exports = [
video.watched = watchedVideos.includes(video.videoId) video.watched = watchedVideos.includes(video.videoId)
}) })
} }
const subscribed = user.isSubscribed(data.authorId) return render(200, "pug/channel.pug", {url, data, subscribed, instanceOrigin})
return render(200, "pug/channel.pug", {req, settings, data, subscribed, instanceOrigin})
} }
} }
] ]

View File

@ -9,7 +9,8 @@ const {Matcher, PatternCompileError} = require("../utils/matcher")
const filterMaxLength = 160 const filterMaxLength = 160
const regexpEnabledText = constants.server_setup.allow_regexp_filters ? "" : "not" const regexpEnabledText = constants.server_setup.allow_regexp_filters ? "" : "not"
function getCategories(user) { function getCategories(req) {
const user = getUser(req)
const filters = user.getFilters() const filters = user.getFilters()
// Sort filters into categories for display. Titles are already sorted. // Sort filters into categories for display. Titles are already sorted.
@ -38,9 +39,7 @@ function getCategories(user) {
module.exports = [ module.exports = [
{ {
route: "/filters", methods: ["GET"], code: async ({req, url}) => { route: "/filters", methods: ["GET"], code: async ({req, url}) => {
const user = getUser(req) const categories = getCategories(req)
const categories = getCategories(user)
const settings = user.getSettingsOrDefaults()
let referrer = url.searchParams.get("referrer") || null let referrer = url.searchParams.get("referrer") || null
let type = null let type = null
@ -55,7 +54,7 @@ module.exports = [
label = url.searchParams.get("label") label = url.searchParams.get("label")
} }
return render(200, "pug/filters.pug", {req, settings, categories, type, contents, label, referrer, filterMaxLength, regexpEnabledText}) return render(200, "pug/filters.pug", {categories, type, contents, label, referrer, filterMaxLength, regexpEnabledText})
} }
}, },
{ {
@ -101,10 +100,8 @@ module.exports = [
return true return true
}, state => { }, state => {
const {type, contents, label, compileError} = state const {type, contents, label, compileError} = state
const user = getUser(req) const categories = getCategories(req)
const categories = getCategories(user) return render(400, "pug/filters.pug", {categories, type, contents, label, compileError, filterMaxLength, regexpEnabledText})
const settings = user.getSettingsOrDefaults()
return render(400, "pug/filters.pug", {req, settings, categories, type, contents, label, compileError, filterMaxLength, regexpEnabledText})
}) })
.last(state => { .last(state => {
const {type, contents, label} = state const {type, contents, label} = state

View File

@ -23,9 +23,10 @@ module.exports = [
const token = user.token const token = user.token
if (add) { if (add) {
await fetchChannel("channel", ucid, settings.instance) await fetchChannel(ucid, settings.instance)
db.prepare( db.prepare(
"INSERT INTO Subscriptions (token, ucid) VALUES (?, ?)" "INSERT INTO Subscriptions (token, ucid) VALUES (?, ?)"
+ " ON CONFLICT (token, ucid) DO UPDATE SET channel_missing = 0"
).run(token, ucid) ).run(token, ucid)
} else { } else {
db.prepare("DELETE FROM Subscriptions WHERE token = ? AND ucid = ?").run(token, ucid) db.prepare("DELETE FROM Subscriptions WHERE token = ? AND ucid = ?").run(token, ucid)
@ -40,6 +41,7 @@ module.exports = [
}), }),
content: "Success, redirecting..." content: "Success, redirecting..."
} }
return redirect(params.get("referrer"), 303)
} else { } else {
return { return {
statusCode: 200, statusCode: 200,

View File

@ -1,35 +1,16 @@
const {render} = require("pinski/plugins") const {render} = require("pinski/plugins")
const {getUser} = require("../utils/getuser")
module.exports = [ module.exports = [
{ {
route: "/", methods: ["GET"], code: async ({req}) => { route: "/", methods: ["GET"], code: async ({req}) => {
const userAgent = req.headers["user-agent"] || "" const userAgent = req.headers["user-agent"] || ""
const mobile = userAgent.toLowerCase().includes("mobile") const mobile = userAgent.toLowerCase().includes("mobile")
const user = getUser(req) return render(200, "pug/home.pug", {mobile})
const settings = user.getSettingsOrDefaults()
return render(200, "pug/home.pug", {req, settings, mobile})
} }
}, },
{ {
route: "/(?:js-)?licenses", methods: ["GET"], code: async ({req}) => { route: "/js-licenses", methods: ["GET"], code: async () => {
const user = getUser(req) return render(200, "pug/js-licenses.pug")
const settings = user.getSettingsOrDefaults()
return render(200, "pug/licenses.pug", {req, settings})
}
},
{
route: "/cant-think", methods: ["GET"], code: async ({req}) => {
const user = getUser(req)
const settings = user.getSettingsOrDefaults()
return render(200, "pug/cant-think.pug", {req, settings})
}
},
{
route: "/privacy", methods: ["GET"], code: async ({req}) => {
const user = getUser(req)
const settings = user.getSettingsOrDefaults()
return render(200, "pug/privacy.pug", {req, settings})
} }
} }
] ]

View File

@ -26,7 +26,7 @@ module.exports = [
const filters = user.getFilters() const filters = user.getFilters()
results = converters.applyVideoFilters(results, filters).videos results = converters.applyVideoFilters(results, filters).videos
return render(200, "pug/search.pug", {req, settings, url, query, results, instanceOrigin}) return render(200, "pug/search.pug", {url, query, results, instanceOrigin})
} }
} }
] ]

View File

@ -7,23 +7,12 @@ const validate = require("../utils/validate")
const V = validate.V const V = validate.V
module.exports = [ module.exports = [
{
route: "/api/settings", methods: ["GET"], code: async ({req}) => {
const user = getUser(req)
const settings = user.getSettingsOrDefaults()
return {
statusCode: 200,
contentType: "application/json",
content: settings
}
}
},
{ {
route: "/settings", methods: ["GET"], code: async ({req}) => { route: "/settings", methods: ["GET"], code: async ({req}) => {
const user = getUser(req) const user = getUser(req)
const settings = user.getSettings() const settings = user.getSettings()
const instances = instancesList.get() const instances = instancesList.get()
return render(200, "pug/settings.pug", {req, constants, user, settings, instances}) return render(200, "pug/settings.pug", {constants, user, settings, instances})
} }
}, },
{ {

View File

@ -11,14 +11,12 @@ module.exports = [
let hasSubscriptions = false let hasSubscriptions = false
let videos = [] let videos = []
let channels = [] let channels = []
let missingChannelCount = 0
let refreshed = null let refreshed = null
if (user.token) { if (user.token) {
// trigger a background refresh, needed if they came back from being inactive // trigger a background refresh, needed if they came back from being inactive
refresher.skipWaiting() refresher.skipWaiting()
// get channels // get channels
channels = db.prepare(`SELECT Channels.* FROM Channels INNER JOIN Subscriptions ON Channels.ucid = Subscriptions.ucid WHERE token = ? ORDER BY name`).all(user.token) channels = db.prepare(`SELECT Channels.* FROM Channels INNER JOIN Subscriptions ON Channels.ucid = Subscriptions.ucid WHERE token = ? ORDER BY name`).all(user.token)
missingChannelCount = channels.reduce((a, c) => a + c.missing, 0)
// get refreshed status // get refreshed status
refreshed = db.prepare(`SELECT min(refreshed) as min, max(refreshed) as max, count(refreshed) as count FROM Channels INNER JOIN Subscriptions ON Channels.ucid = Subscriptions.ucid WHERE token = ?`).get(user.token) refreshed = db.prepare(`SELECT min(refreshed) as min, max(refreshed) as max, count(refreshed) as count FROM Channels INNER JOIN Subscriptions ON Channels.ucid = Subscriptions.ucid WHERE token = ?`).get(user.token)
// get watched videos // get watched videos
@ -39,7 +37,7 @@ module.exports = [
} }
const settings = user.getSettingsOrDefaults() const settings = user.getSettingsOrDefaults()
const instanceOrigin = settings.instance const instanceOrigin = settings.instance
return render(200, "pug/subscriptions.pug", {req, url, settings, hasSubscriptions, videos, channels, missingChannelCount, refreshed, timeToPastText, instanceOrigin}) return render(200, "pug/subscriptions.pug", {url, settings, hasSubscriptions, videos, channels, refreshed, timeToPastText, instanceOrigin})
} }
} }
] ]

View File

@ -3,8 +3,8 @@ const {render} = require("pinski/plugins")
module.exports = [ module.exports = [
{ {
route: "/takedown", methods: ["GET"], code: async ({req}) => { route: "/takedown", methods: ["GET"], code: async () => {
return render(200, "pug/takedown.pug", {req, constants}) return render(200, "pug/takedown.pug", {constants})
} }
} }
] ]

View File

@ -111,7 +111,7 @@ module.exports = [
// Check if playback is allowed // Check if playback is allowed
const videoTakedownInfo = db.prepare("SELECT id, org, url FROM TakedownVideos WHERE id = ?").get(id) const videoTakedownInfo = db.prepare("SELECT id, org, url FROM TakedownVideos WHERE id = ?").get(id)
if (videoTakedownInfo) { if (videoTakedownInfo) {
return render(451, "pug/takedown-video.pug", Object.assign({req, settings}, videoTakedownInfo)) return render(451, "pug/takedown-video.pug", videoTakedownInfo)
} }
// Media fragment // Media fragment
@ -129,7 +129,7 @@ module.exports = [
// Work out how to fetch the video // Work out how to fetch the video
if (req.method === "GET") { if (req.method === "GET") {
if (settings.local) { // skip to the local fetching page, which will then POST video data in a moment if (settings.local) { // skip to the local fetching page, which will then POST video data in a moment
return render(200, "pug/local-video.pug", {req, settings, id}) return render(200, "pug/local-video.pug", {id})
} }
var instanceOrigin = settings.instance var instanceOrigin = settings.instance
var outURL = `${instanceOrigin}/api/v1/videos/${id}` var outURL = `${instanceOrigin}/api/v1/videos/${id}`
@ -153,7 +153,7 @@ module.exports = [
// automatically add the entry to the videos list, so it won't be fetched again // automatically add the entry to the videos list, so it won't be fetched again
const args = {id, ...channelTakedownInfo} const args = {id, ...channelTakedownInfo}
db.prepare("INSERT INTO TakedownVideos (id, org, url) VALUES (@id, @org, @url)").run(args) db.prepare("INSERT INTO TakedownVideos (id, org, url) VALUES (@id, @org, @url)").run(args)
return render(451, "pug/takedown-video.pug", Object.assign({req, settings}, channelTakedownInfo)) return render(451, "pug/takedown-video.pug", channelTakedownInfo)
} }
// process stream list ordering // process stream list ordering
@ -199,7 +199,7 @@ module.exports = [
} }
return render(200, "pug/video.pug", { return render(200, "pug/video.pug", {
req, url, video, formats, subscribed, instanceOrigin, mediaFragment, autoplay, continuous, url, video, formats, subscribed, instanceOrigin, mediaFragment, autoplay, continuous,
sessionWatched, sessionWatchedNext, settings sessionWatched, sessionWatchedNext, settings
}) })
@ -225,7 +225,7 @@ module.exports = [
// Create appropriate formatted message // Create appropriate formatted message
const message = render(0, `pug/errors/${errorType}.pug`, locals).content const message = render(0, `pug/errors/${errorType}.pug`, locals).content
return render(500, "pug/video.pug", {video: {videoId: id}, error: true, message, req, settings}) return render(500, "pug/video.pug", {video: {videoId: id}, error: true, message})
} }
} }
} }

View File

@ -15,8 +15,8 @@ const prepared = {
channel_refreshed_update: db.prepare( channel_refreshed_update: db.prepare(
"UPDATE Channels SET refreshed = ? WHERE ucid = ?" "UPDATE Channels SET refreshed = ? WHERE ucid = ?"
), ),
channel_mark_as_missing: db.prepare( unsubscribe_all_from_channel: db.prepare(
"UPDATE Channels SET missing = 1, missing_reason = ? WHERE ucid = ?" "UPDATE Subscriptions SET channel_missing = 1 WHERE ucid = ?"
) )
} }
@ -35,7 +35,7 @@ class RefreshQueue {
// get the next set of scheduled channels to refresh // get the next set of scheduled channels to refresh
const afterTime = Date.now() - constants.caching.seen_token_subscriptions_eligible const afterTime = Date.now() - constants.caching.seen_token_subscriptions_eligible
const channels = db.prepare( const channels = db.prepare(
"SELECT DISTINCT Subscriptions.ucid FROM SeenTokens INNER JOIN Subscriptions ON SeenTokens.token = Subscriptions.token INNER JOIN Channels ON Channels.ucid = Subscriptions.ucid WHERE Channels.missing = 0 AND SeenTokens.seen > ? ORDER BY SeenTokens.seen DESC" "SELECT DISTINCT Subscriptions.ucid FROM SeenTokens INNER JOIN Subscriptions ON SeenTokens.token = Subscriptions.token AND SeenTokens.seen > ? WHERE Subscriptions.channel_missing = 0 ORDER BY SeenTokens.seen DESC"
).pluck().all(afterTime) ).pluck().all(afterTime)
this.addLast(channels) this.addLast(channels)
this.lastLoadTime = Date.now() this.lastLoadTime = Date.now()
@ -72,12 +72,11 @@ class Refresher {
this.refreshQueue = new RefreshQueue() this.refreshQueue = new RefreshQueue()
this.state = this.sym.ACTIVE this.state = this.sym.ACTIVE
this.waitingTimeout = null this.waitingTimeout = null
this.lastFakeNotFoundTime = 0
this.next() this.next()
} }
refreshChannel(ucid) { refreshChannel(ucid) {
return fetch(`${constants.server_setup.local_instance_origin}/api/v1/channels/${ucid}/latest`).then(res => res.json()).then(/** @param {any} root */ root => { return fetch(`${constants.server_setup.local_instance_origin}/api/v1/channels/${ucid}/latest`).then(res => res.json()).then(root => {
if (Array.isArray(root)) { if (Array.isArray(root)) {
root.forEach(video => { root.forEach(video => {
// organise // organise
@ -90,24 +89,11 @@ class Refresher {
prepared.channel_refreshed_update.run(Date.now(), ucid) prepared.channel_refreshed_update.run(Date.now(), ucid)
// console.log(`updated ${root.length} videos for channel ${ucid}`) // console.log(`updated ${root.length} videos for channel ${ucid}`)
} else if (root.identifier === "PUBLISHED_DATES_NOT_PROVIDED") { } else if (root.identifier === "PUBLISHED_DATES_NOT_PROVIDED") {
// nothing we can do. skip this iteration. return [] // nothing we can do. skip this iteration.
} else if (root.identifier === "NOT_FOUND") { } else if (root.identifier === "NOT_FOUND") {
// YouTube sometimes returns not found for absolutely no reason. // the channel does not exist. we should unsubscribe all users so we don't try again.
// There is no way to distinguish between a fake missing channel and a real missing channel without requesting the real endpoint. // console.log(`channel ${ucid} does not exist, unsubscribing all users`)
// These fake missing channels often happen in bursts, which is why there is a cooldown. prepared.unsubscribe_all_from_channel.run(ucid)
const timeSinceLastFakeNotFound = Date.now() - this.lastFakeNotFoundTime
if (timeSinceLastFakeNotFound >= constants.caching.subscriptions_refesh_fake_not_found_cooldown) {
// We'll request the real endpoint to verify.
fetch(`${constants.server_setup.local_instance_origin}/api/v1/channels/${ucid}`).then(res => res.json()).then(/** @param {any} root */ root => {
if (root.error && (root.identifier === "NOT_FOUND" || root.identifier === "ACCOUNT_TERMINATED")) {
// The channel is really gone, and we should mark it as missing for everyone.
prepared.channel_mark_as_missing.run(root.error, ucid)
} else {
// The channel is not actually gone and YouTube is trolling us.
this.lastFakeNotFoundTime = Date.now()
}
})
} // else youtube is currently trolling us, skip this until later.
} else { } else {
throw new Error(root.error) throw new Error(root.error)
} }

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/static/images/mstile-150x150.png"/>
<TileColor>#2b5797</TileColor>
</tile>
</msapplication>
</browserconfig>

View File

@ -1,34 +0,0 @@
{
"name": "CloudTube",
"short_name": "CloudTube",
"icons": [
{
"src": "/static/images/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/static/images/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/static/images/maskable-icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/static/images/maskable-icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"theme_color": "#36393f",
"background_color": "#36393f",
"start_url": "/",
"display": "standalone"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 578 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="8" viewBox="0 0 5.821 2.117"><path d="M1.269.53l.767.793h.161L2.964.53h.211v.265L2.117 1.852 1.058.794V.529z" fill="#202020" paint-order="markers stroke fill"/></svg>

Before

Width:  |  Height:  |  Size: 226 B

View File

Before

Width:  |  Height:  |  Size: 225 B

After

Width:  |  Height:  |  Size: 225 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 710 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 491 B

View File

@ -1,33 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.14, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M2667 4020 c-115 -29 -296 -105 -387 -162 -54 -34 -182 -133 -207
-159 -12 -14 -33 -49 -47 -79 -13 -30 -48 -80 -78 -110 -46 -47 -170 -221
-180 -252 -3 -7 -39 1 -96 21 -50 17 -147 43 -214 57 -111 23 -137 24 -258 18
-87 -4 -154 -13 -190 -24 -30 -10 -82 -24 -115 -31 -90 -17 -163 -59 -231
-131 -184 -197 -233 -305 -250 -554 -6 -90 -3 -115 31 -279 31 -152 44 -193
81 -265 24 -47 59 -120 78 -162 19 -43 56 -106 82 -140 66 -88 173 -196 214
-217 126 -65 482 -83 711 -35 68 14 156 36 194 49 95 32 317 43 420 22 39 -8
104 -18 145 -22 41 -3 109 -12 150 -20 166 -31 322 -37 825 -33 420 4 509 7
555 21 30 9 89 24 131 35 156 39 312 154 413 306 20 30 41 58 46 61 5 3 34 52
64 108 43 82 58 123 75 206 26 126 27 278 5 387 -18 86 -32 121 -74 174 -15
19 -34 49 -43 66 -34 67 -174 199 -262 248 -16 9 -52 32 -78 51 -27 19 -67 39
-90 45 -23 6 -71 24 -107 39 -64 28 -340 91 -397 91 -28 0 -30 4 -56 88 -15
48 -40 108 -55 134 -38 64 -224 269 -288 318 -63 48 -182 104 -250 118 -75 16
-224 22 -267 12z m-428 -984 c7 -8 33 -16 59 -19 183 -20 276 -46 447 -127 61
-29 128 -58 149 -66 22 -8 67 -32 100 -53 34 -21 84 -50 113 -65 29 -14 68
-40 88 -57 31 -27 35 -37 35 -78 0 -40 -5 -51 -32 -77 -53 -49 -130 -100 -171
-114 -21 -6 -57 -26 -80 -44 -49 -37 -70 -49 -192 -106 -49 -23 -99 -51 -111
-61 -11 -10 -45 -32 -75 -48 -30 -16 -88 -47 -129 -70 -95 -52 -134 -54 -177
-8 -74 76 -121 287 -133 592 -4 121 -13 236 -19 255 -19 60 -14 101 16 132 30
31 91 39 112 14z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -1 +1 @@
<svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" width="25" height="25" viewBox="0 0 6.615 6.615"><title>Settings</title><path d="M2.64 0s-.03.94-.424 1.215C1.823 1.49.885.894.885.894L.112 2.232.109 2.23l.003.002c.01.006.797.499.838.974.042.478-.944.992-.944.992l.77 1.34s.83-.442 1.265-.24c.435.204.387 1.314.387 1.314l1.546.003s.032-.94.425-1.215c.393-.275 1.331.321 1.331.321l.775-1.338s-.798-.496-.84-.974c-.041-.478.944-.993.944-.993l-.77-1.34s-.83.443-1.265.24C4.14 1.113 4.187.002 4.187.002zm.688 2.25a1.106 1.106 0 110 2.211 1.106 1.106 0 010-2.21z" paint-order="fill markers stroke"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" viewBox="0 0 6.615 6.615"><path d="M2.64 0s-.03.94-.424 1.215C1.823 1.49.885.894.885.894L.112 2.232.109 2.23l.003.002c.01.006.797.499.838.974.042.478-.944.992-.944.992l.77 1.34s.83-.442 1.265-.24c.435.204.387 1.314.387 1.314l1.546.003s.032-.94.425-1.215c.393-.275 1.331.321 1.331.321l.775-1.338s-.798-.496-.84-.974c-.041-.478.944-.993.944-.993l-.77-1.34s-.83.443-1.265.24C4.14 1.113 4.187.002 4.187.002zm.688 2.25a1.106 1.106 0 110 2.211 1.106 1.106 0 010-2.21z" fill="#c4c4c4" paint-order="fill markers stroke"/></svg>

Before

Width:  |  Height:  |  Size: 611 B

After

Width:  |  Height:  |  Size: 582 B

View File

@ -1 +1 @@
<svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" width="30" height="25" viewBox="0 0 7.937 6.615"><title>Subscriptions</title><path d="M2.373 0h3.191a.52.52 0 01.521.521H1.852A.52.52 0 012.373 0zm-.91.794h5.011c.371 0 .67.298.67.67v.123l-6.35-.016v-.108c0-.37.298-.67.67-.67zm-.405 1.058C.472 1.852 0 2.184 0 2.77v2.868c0 .586.472.977 1.058.977H6.88c.586 0 1.059-.39 1.059-.977V2.77c0-.586-.473-.918-1.059-.918zM5.3 4.017c.167.099.19.276.012.366l-2.098.985c-.131.077-.304-.002-.302-.19V3.29c0-.203.18-.333.34-.245z" paint-order="fill markers stroke"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="30" height="25" viewBox="0 0 7.937 6.615"><path d="M2.373 0h3.191a.52.52 0 01.521.521H1.852A.52.52 0 012.373 0zm-.91.794h5.011c.371 0 .67.298.67.67v.123l-6.35-.016v-.108c0-.37.298-.67.67-.67zm-.405 1.058C.472 1.852 0 2.184 0 2.77v2.868c0 .586.472.977 1.058.977H6.88c.586 0 1.059-.39 1.059-.977V2.77c0-.586-.473-.918-1.059-.918zM5.3 4.017c.167.099.19.276.012.366l-2.098.985c-.131.077-.304-.002-.302-.19V3.29c0-.203.18-.333.34-.245z" fill="#c4c4c4" paint-order="fill markers stroke"/></svg>

Before

Width:  |  Height:  |  Size: 569 B

After

Width:  |  Height:  |  Size: 535 B

1305
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,11 +10,11 @@
"author": "", "author": "",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
"better-sqlite3": "^7.4.5", "better-sqlite3": "^7.1.0",
"cookie": "^0.4.1", "cookie": "^0.4.1",
"denque": "^1.5.1", "denque": "^1.4.1",
"mixin-deep": "^2.0.1", "mixin-deep": "^2.0.1",
"node-fetch": "^2.6.6", "node-fetch": "^2.6.0",
"pinski": "git+https://git.sr.ht/~cadence/nodejs-pinski#9653807f309aee34c8c63ce4e6ee760cccbfdf0d" "pinski": "git+https://git.sr.ht/~cadence/nodejs-pinski#210be3cfacbd93d44e104698a29abd39494a6271"
} }
} }

View File

@ -18,7 +18,6 @@ mixin leave(index, prefix="No more?", final)
a(href="/search?q=cats+being+cute") [BLISS] a(href="/search?q=cats+being+cute") [BLISS]
block content block content
include includes/video-list-item
main.cant-think-page main.cant-think-page
- let src = constants.server_setup.cant_think_narration_url - let src = constants.server_setup.cant_think_narration_url
if src if src
@ -72,8 +71,3 @@ block content
p You know what you must do. p You know what you must do.
p.ultimatum: a(href="#i-understand").border-look I know what I must do. p.ultimatum: a(href="#i-understand").border-look I know what I must do.
a#i-understand a#i-understand
p.the-end Well, looks like you reached the end of the content.#[br]You know what happens now.
h3 Recommended videos
+video_list_item("related-video", {videoId: "jKKCF_Bqtw4", title: "dopamine review", author: "Jreg", authorId: "UCGSGPehp0RWfca-kENgBJ9Q", viewCountText: "84,651 views", second__lengthText: "3:10"})
+video_list_item("related-video", {videoId: "gLYWLobR248", title: "I Watch My YouTube Videos At 2x Speed", author: "Jreg", authorId: "UCGSGPehp0RWfca-kENgBJ9Q", viewCountText: "125,625 views", second__lengthText: "2:24"})

View File

@ -1,27 +0,0 @@
extends includes/layout
include includes/video-list-item
include includes/subscribe-button
block head
title= `${data.row ? data.row.name : "Deleted channel"} - CloudTube`
script(type="module" src=getStaticURL("html", "/static/js/channel.js"))
block content
main.channel-page
if data.row
.channel-data
.info
- const iconURL = data.row.icon_url
if iconURL
.logo
img(src=iconURL alt="").thumbnail-image
.about
h1.name= data.row.name
+subscribe_button(data.ucid, subscribed, `/channel/${data.ucid}`).subscribe-button.base-border-look
.channel-error
div= data.message
if data.missing && subscribed
.you-should-unsubscribe To remove this channel from your subscriptions list, click Unsubscribe.

View File

@ -3,21 +3,8 @@ html
head head
meta(charset="utf-8") meta(charset="utf-8")
meta(name="viewport" content="width=device-width, initial-scale=1") meta(name="viewport" content="width=device-width, initial-scale=1")
- const theme = settings && ["dark", "light", "edgeless-light"][settings.theme] || "dark" link(rel="stylesheet" type="text/css" href=getStaticURL("sass", "/main.sass"))
link(rel="stylesheet" type="text/css" href=getStaticURL("sass", `/${theme}.sass`))
script(type="module" src=getStaticURL("html", "/static/js/focus.js")) script(type="module" src=getStaticURL("html", "/static/js/focus.js"))
link(rel="apple-touch-icon" sizes="180x180" href="/static/images/apple-touch-icon.png")
link(rel="icon" type="image/png" sizes="32x32" href="/static/images/favicon-32x32.png")
link(rel="icon" type="image/png" sizes="16x16" href="/static/images/favicon-16x16.png")
link(rel="manifest" href="/site.webmanifest")
link(rel="mask-icon" href="/static/images/safari-pinned-tab.svg" color="#5bbad5")
link(rel="shortcut icon" href="/static/images/favicon.ico")
meta(name="apple-mobile-web-app-title" content="CloudTube")
meta(name="application-name" content="CloudTube")
meta(name="msapplication-TileColor" content="#2b5797")
meta(name="msapplication-config" content="/browserconfig.xml")
meta(name="theme-color" content="#36393f")
block head block head
body.show-focus body.show-focus
@ -26,14 +13,15 @@ html
if showNav if showNav
nav.main-nav nav.main-nav
.links .links
if req && req.headers && "x-insecure" in req.headers a(href="/").link.home CloudTube
a(href="/").link.home CloudTube - Insecure
else
a(href="/").link.home CloudTube
a(href="/subscriptions" title="Subscriptions").link.icon-link a(href="/subscriptions" title="Subscriptions").link.icon-link
!= icons.get("subscriptions") svg(width=30 height=25)
image(href=getStaticURL("html", "/static/images/subscriptions.svg") alt="Subscriptions.").icon
title Subscriptions
a(href="/settings" title="Settings").link.icon-link a(href="/settings" title="Settings").link.icon-link
!= icons.get("settings") svg(width=25 height=25)
image(href=getStaticURL("html", "/static/images/settings.svg") alt="Settings.").icon
title Settings
form(method="get" action="/search").search-form form(method="get" action="/search").search-form
input(type="text" placeholder="Search" aria-label="Search a video" name="q" autocomplete="off" value=query).search input(type="text" placeholder="Search" aria-label="Search a video" name="q" autocomplete="off" value=query).search

View File

@ -36,16 +36,6 @@ block content
form(method="post" action="/settings") form(method="post" action="/settings")
+fieldset("Settings") +fieldset("Settings")
+select({
id: "theme",
label: "Theme",
options: [
{value: "0", text: "Standard dark"},
{value: "1", text: "Standard light"},
{value: "2", text: "Edgeless light"}
]
})
+input({ +input({
id: "instance", id: "instance",
label: "Instance", label: "Instance",

View File

@ -11,24 +11,12 @@ block content
if hasSubscriptions if hasSubscriptions
section section
details.channels-details details.channels-details
summary summary #{channels.length} subscriptions
| #{channels.length} subscriptions
if missingChannelCount === 1
= ` - ${missingChannelCount} channel is gone`
else if missingChannelCount > 1
= ` - ${missingChannelCount} channels are gone`
.channels-list .channels-list
for channel in channels for channel in channels
a(href=`/channel/${channel.ucid}`).channel-item a(href=`/channel/${channel.ucid}`).channel-item
img(src=channel.icon_url width=512 height=512 alt="").thumbnail img(src=channel.icon_url width=512 height=512 alt="").thumbnail
div span.name= channel.name
div.name= channel.name
if channel.missing
div.missing-reason
if channel.missing_reason
= channel.missing_reason
else
| This channel appears to be deleted or terminated. Click to check it.
if refreshed if refreshed
section section
@ -43,8 +31,8 @@ block content
if settings.save_history if settings.save_history
input(type="checkbox" id="watched-videos-display") input(type="checkbox" id="watched-videos-display")
.checkbox-hider__container .watched-videos-display-container
label(for="watched-videos-display").checkbox-hider__label Hide watched videos label(for="watched-videos-display").watched-videos-display-label Hide watched videos
each video in videos each video in videos
+video_list_item("subscriptions-video", video, instanceOrigin, {showMarkWatched: settings.save_history && !video.watched}) +video_list_item("subscriptions-video", video, instanceOrigin, {showMarkWatched: settings.save_history && !video.watched})

View File

@ -21,7 +21,6 @@ block content
noscript noscript
meta(http-equiv="refresh" content=`${video.lengthSeconds+5};url=/watch?v=${first.videoId}&continuous=1&session-watched=${sessionWatchedNext}`) meta(http-equiv="refresh" content=`${video.lengthSeconds+5};url=/watch?v=${first.videoId}&continuous=1&session-watched=${sessionWatchedNext}`)
.video-page(class={ .video-page(class={
"video-page--recommended-side": settings.recommended_mode === 0,
"video-page--recommended-below": settings.recommended_mode === 1, "video-page--recommended-below": settings.recommended_mode === 1,
"video-page--recommended-hidden": settings.recommended_mode === 2 "video-page--recommended-hidden": settings.recommended_mode === 2
}) })
@ -77,7 +76,7 @@ block content
img(src="/static/images/search.svg" width=17 height=17 alt="").button-icon img(src="/static/images/search.svg" width=17 height=17 alt="").button-icon
| Search | Search
//- button.border-look#share Share //- button.border-look#share Share
a(href=`https://yewtu.be/watch?v=${video.videoId}`).border-look YewTube a(href=`https://www.youtube.com/watch?v=${video.videoId}#cloudtube`).border-look YouTube
a(href=`https://redirect.invidious.io/watch?v=${video.videoId}`).border-look Invidious a(href=`https://redirect.invidious.io/watch?v=${video.videoId}`).border-look Invidious
.description#description!= video.descriptionHtml .description#description!= video.descriptionHtml

View File

@ -47,8 +47,8 @@ block content
img(src="/static/images/search.svg" width=17 height=17 alt="").button-icon img(src="/static/images/search.svg" width=17 height=17 alt="").button-icon
| Search | Search
//- button.border-look#share Share //- button.border-look#share Share
a(target="_blank", href=`/watch?v=${video.videoId}`).border-look CloudTube (not embedded) a(target="_blank", href=`/watch?v=${video.videoId}#cloudtube`).border-look CloudTube (not embedded)
a(target="_blank", href=`https://yewtu.be/watch?v=${video.videoId}`).border-look YewTube a(target="_blank", href=`https://www.youtube.com/watch?v=${video.videoId}#cloudtube`).border-look YouTube
a(target="_blank", href=`https://redirect.invidious.io/watch?v=${video.videoId}`).border-look Invidious a(target="_blank", href=`https://redirect.invidious.io/watch?v=${video.videoId}`).border-look Invidious
else else
//- error //- error

View File

@ -1,9 +0,0 @@
@use "themes/dark" as *
@use "includes/main" with ($_theme: $theme)
@use "theme-modules/edgeless" with ($_theme: $theme)
// navigation shadow
.main-nav
position: relative // needed for box shadow to overlap related videos section
box-shadow: 0px 0px 20px 5px rgba(0, 0, 0, 0.1)

View File

@ -1,4 +0,0 @@
@use "themes/edgeless-light" as *
@use "includes/main" with ($_theme: $theme)
@use "theme-modules/edgeless" with ($_theme: $theme)

View File

@ -1,33 +0,0 @@
$_theme: () !default
@use "sass:selector"
// preload second-level includes with the theme (there will be conflicts due to reconfiguration they are loaded individually)
// this isn't _exactly_ what @forward is supposed to be used for, but it's the best option here
@forward "video-list-item" show _ with ($_theme: $_theme)
@forward "forms" show _ with ($_theme: $_theme)
@forward "buttons" show _ with ($_theme: $_theme)
@use "base" with ($_theme: $_theme)
@use "video-page" with ($_theme: $_theme)
@use "video-embed" with ($_theme: $_theme)
@use "search-page" with ($_theme: $_theme)
@use "home-page" with ($_theme: $_theme)
@use "channel-page" with ($_theme: $_theme)
@use "subscriptions-page" with ($_theme: $_theme)
@use "settings-page" with ($_theme: $_theme)
@use "cant-think-page" with ($_theme: $_theme)
@use "privacy-page" with ($_theme: $_theme)
@use "licenses-page" with ($_theme: $_theme)
@use "filters-page" with ($_theme: $_theme)
@use "takedown-page" with ($_theme: $_theme)
@use "nav" with ($_theme: $_theme)
@use "footer" with ($_theme: $_theme)
@font-face
font-family: "Bariol"
src: url(/static/fonts/bariol.woff?statichash=1)
.button-container
display: flex
flex-wrap: wrap

View File

@ -1,16 +0,0 @@
$_theme: () !default
@use "sass:map"
.takedown-page
max-width: 700px
margin: 0 auto
.new-section
margin-top: 200px
.important-section
padding: 4px 20px
border: 1px solid map.get($_theme, "edge-grey")
color: map.get($_theme, "fg-bright")
background-color: map.get($_theme, "bg-1")

View File

@ -1,10 +1,8 @@
$_theme: () !default @use "colors.sass" as c
@use "sass:map"
body body
background-color: map.get($_theme, "bg-2") background-color: c.$bg-dark
color: map.get($_theme, "fg-main") color: c.$fg-main
font-family: "Bariol", sans-serif font-family: "Bariol", sans-serif
font-size: 18px font-size: 18px
margin: 0 margin: 0
@ -15,13 +13,13 @@ body
flex-direction: column flex-direction: column
a a
color: map.get($_theme, "link") color: c.$link
pre, code pre, code
font-size: 0.88em font-size: 0.88em
code code
background: map.get($_theme, "bg-1") background: c.$bg-darker
padding: 3px 5px padding: 3px 5px
border-radius: 4px border-radius: 4px
@ -34,7 +32,7 @@ button
cursor: pointer cursor: pointer
::placeholder ::placeholder
color: map.get($_theme, "placeholder") color: #c4c4c4
opacity: 1 opacity: 1
// focus section // focus section
@ -50,20 +48,19 @@ button
select:-moz-focusring select:-moz-focusring
color: transparent color: transparent
text-shadow: 0 0 0 map.get($_theme, "fg-bright") text-shadow: 0 0 0 c.$fg-bright
body.show-focus body.show-focus
a, select, button, input, video, summary a, select, button, input, video, summary
&:focus &:focus
outline: 2px dotted map.get($_theme, "fg-main") outline: 2px dotted #ddd
video video
background-color: black background-color: black
details details
background-color: map.get($_theme, "bg-3") background-color: c.$bg-accent-x
padding: 12px padding: 12px
border: 1px solid map.get($_theme, "edge-grey")
border-radius: 8px border-radius: 8px
summary summary
@ -71,22 +68,20 @@ details
line-height: 1 line-height: 1
margin-bottom: 0 margin-bottom: 0
user-select: none user-select: none
color: map.get($_theme, "fg-main") color: c.$fg-main
&[open] summary &[open] summary
padding-bottom: 12px margin-bottom: 16px
border-bottom: 1px solid map.get($_theme, "edge-grey")
margin-bottom: 8px
table table
background-color: map.get($_theme, "bg-1") background-color: c.$bg-darker
table, td, th table, td, th
border: 1px solid map.get($_theme, "edge-grey") border: 1px solid c.$edge-grey
border-collapse: collapse border-collapse: collapse
td, th td, th
padding: 4px 8px padding: 4px 8px
thead, tr:nth-child(even) thead, tr:nth-child(even)
background-color: map.get($_theme, "bg-0") background-color: c.$bg-darkest

View File

@ -1,12 +1,10 @@
$_theme: () !default
@use "sass:selector" @use "sass:selector"
@use "sass:map" @use "colors.sass" as c
@mixin button-base @mixin button-base
-webkit-appearance: none -webkit-appearance: none
-moz-appearance: none -moz-appearance: none
color: map.get($_theme, "fg-bright") color: c.$fg-bright
border: none border: none
border-radius: 4px border-radius: 4px
padding: 8px padding: 8px
@ -16,7 +14,7 @@ $_theme: () !default
@at-root #{selector.unify(&, "select")} @at-root #{selector.unify(&, "select")}
padding: 8px 27px 8px 8px padding: 8px 27px 8px 8px
background: map.get($_theme, "image-dropdown") right 53% no-repeat map.get($_theme, "bg-4") background: url(/static/images/arrow-down-wide.svg) right 53% no-repeat c.$bg-accent-x
@at-root #{selector.unify(&, "a")} @at-root #{selector.unify(&, "a")}
padding: 7px 8px padding: 7px 8px
@ -33,12 +31,12 @@ $_theme: () !default
@mixin button-bg @mixin button-bg
@include button-base @include button-base
background-color: map.get($_theme, "bg-4") background-color: c.$bg-accent-x
@mixin border-button @mixin border-button
@include button-bg @include button-bg
border: 1px solid map.get($_theme, "edge-grey") border: 1px solid c.$edge-grey
@mixin button-size @mixin button-size
margin: 4px margin: 4px
@ -46,10 +44,10 @@ $_theme: () !default
@mixin button-hover @mixin button-hover
&:hover &:hover
background-color: map.get($_theme, "bg-3") background-color: c.$bg-accent
&:active &:active
background-color: map.get($_theme, "bg-2") background-color: c.$bg-dark
.base-border-look .base-border-look
@include border-button @include border-button
@ -64,13 +62,13 @@ $_theme: () !default
@include button-size @include button-size
-webkit-appearance: none -webkit-appearance: none
-moz-appearance: none -moz-appearance: none
color: map.get($_theme, "fg-bright") color: c.$fg-bright
text-decoration: none text-decoration: none
line-height: 1.25 line-height: 1.25
margin: 0 margin: 0
padding: 8px 20px padding: 8px 20px
background: map.get($_theme, "bg-3") background: c.$bg-accent
border: solid map.get($_theme, "edge-grey") border: solid c.$bg-darker
border-width: 1px 0px 0px border-width: 1px 0px 0px
text-align: left text-align: left
@ -78,7 +76,7 @@ $_theme: () !default
border-width: 1px 0px 1px border-width: 1px 0px 1px
&:hover &:hover
background: map.get($_theme, "bg-4") background: c.$bg-accent-x
&:active &:active
background: map.get($_theme, "bg-1") background: c.$bg-darker

View File

@ -1,7 +1,5 @@
$_theme: () !default
@use "sass:list" @use "sass:list"
@use "sass:map" @use "colors.sass" as c
.cant-think-page .cant-think-page
.main-nav .main-nav
@ -11,14 +9,13 @@ $_theme: () !default
text-align: left text-align: left
max-width: 572px max-width: 572px
padding-top: 40px padding-top: 40px
margin-bottom: 12vh
border-radius: 0px 0px 16px 16px border-radius: 0px 0px 16px 16px
box-sizing: border-box box-sizing: border-box
.page-narration .page-narration
background-color: map.get($_theme, "bg-3") background-color: c.$bg-accent
border: 1px solid map.get($_theme, "edge-grey") border: 1px solid #aaa
color: map.get($_theme, "fg-bright") color: #fff
border-radius: 0 border-radius: 0
padding: 16px padding: 16px
margin: 40px auto 60px margin: 40px auto 60px
@ -31,7 +28,7 @@ $_theme: () !default
.leave .leave
margin: 26px 32px !important margin: 26px 32px !important
color: map.get($_theme, "fg-dim") color: #aaa
$sizes: 14px 16px 21px 30px 72px $sizes: 14px 16px 21px 30px 72px
@each $size in $sizes @each $size in $sizes
@ -40,7 +37,7 @@ $_theme: () !default
&.leave__final &.leave__final
font-weight: bold font-weight: bold
color: map.get($_theme, "fg-bright") color: #f2f2f2
text-align: center text-align: center
.leave__actions .leave__actions
@ -57,12 +54,6 @@ $_theme: () !default
.ultimatum .ultimatum
margin-top: 32px !important margin-top: 32px !important
.the-end
margin: 120px 0px 1em !important
.thumbnail__more
display: none
#i-understand #i-understand
display: flex display: flex
height: 20px height: 20px
@ -74,7 +65,6 @@ $_theme: () !default
&:target &:target
position: fixed position: fixed
z-index: 1
top: 0 top: 0
bottom: 0 bottom: 0
left: 0 left: 0

View File

@ -1,8 +1,6 @@
$_theme: () !default @use "colors.sass" as c
@use "video-list-item.sass" as *
@use "sass:map" @use "_dimensions.sass" as dimensions
@use "_dimensions" as dimensions
@use "video-list-item" as *
.channel-page .channel-page
padding: 40px 20px 20px padding: 40px 20px 20px
@ -19,7 +17,7 @@ $_theme: () !default
align-self: flex-start align-self: flex-start
.channel-data .channel-data
background-color: map.get($_theme, "bg-1") background-color: c.$bg-darker
padding: 24px padding: 24px
margin: 12px 0px 24px margin: 12px 0px 24px
border-radius: 8px border-radius: 8px
@ -46,11 +44,11 @@ $_theme: () !default
.name .name
font-size: 30px font-size: 30px
font-weight: normal font-weight: normal
color: map.get($_theme, "fg-bright") color: c.$fg-bright
margin: 0 margin: 0
.subscribers .subscribers
color: map.get($_theme, "fg-main") color: c.$fg-main
font-size: 18px font-size: 18px
.subscribe-form .subscribe-form
@ -63,8 +61,7 @@ $_theme: () !default
line-height: 1 line-height: 1
border-radius: 8px border-radius: 8px
font-size: 22px font-size: 22px
background-color: map.get($_theme, "power-deep") background-color: c.$power-deep
color: map.get($_theme, "power-fg")
border: none border: none
.description .description
@ -74,19 +71,6 @@ $_theme: () !default
.channel-video .channel-video
@include channel-video @include channel-video
.channel-error
background-color: map.get($_theme, "bg-1")
padding: 24px
margin: 12px 0px 24px
border-radius: 8px
border: 1px solid map.get($_theme, "edge-grey")
font-size: 20px
color: map.get($_theme, "fg-warning")
.you-should-unsubscribe
margin-top: 20px
color: map.get($_theme, "fg-main")
.about-description // class provided by youtube .about-description // class provided by youtube
pre pre
font-size: inherit font-size: inherit

17
sass/includes/colors.sass Normal file
View File

@ -0,0 +1,17 @@
$bg-darkest: #202123
$bg-darker: #303336
$bg-dark: #36393f
$bg-accent: #4f5359
$bg-accent-x: #3f4247
$bg-accent-area: #44474b
$fg-bright: #fff
$fg-main: #ddd
$fg-dim: #bbb
$fg-warning: #fdca6d
$edge-grey: #a0a0a0
$link: #8ac2f9
$power-deep: #c62727

View File

@ -1,12 +1,10 @@
$_theme: () !default @use "colors.sass" as c
@use "sass:map"
@mixin filter-notice @mixin filter-notice
margin-top: 24px margin-top: 24px
padding: 12px padding: 12px
border-radius: 8px border-radius: 8px
background-color: map.get($_theme, "bg-1") background-color: c.$bg-darker
white-space: pre-line white-space: pre-line
.filters-page .filters-page
@ -22,23 +20,23 @@ $_theme: () !default
.filter-confirmation-notice .filter-confirmation-notice
@include filter-notice @include filter-notice
color: map.get($_theme, "fg-warning") color: c.$fg-warning
.filter-compile-error .filter-compile-error
@include filter-notice @include filter-notice
&__header &__header
color: map.get($_theme, "fg-warning") color: c.$fg-warning
&__trace &__trace
background-color: map.get($_theme, "bg-0") background-color: c.$bg-darkest
padding: 6px padding: 6px
.save-filter .save-filter
margin-top: 12px margin-top: 12px
.border-look .border-look
background-color: map.get($_theme, "bg-1") background-color: c.$bg-darker
font-size: 22px font-size: 22px
padding: 7px 16px 8px padding: 7px 16px 8px
font-size: 24px font-size: 24px
@ -50,17 +48,17 @@ $_theme: () !default
.filter .filter
display: flex display: flex
padding: 5px 0 padding: 5px 0
border-top: 1px solid map.get($_theme, "edge-grey") border-top: 1px solid c.$edge-grey
&:last-child &:last-child
border-bottom: 1px solid map.get($_theme, "edge-grey") border-bottom: 1px solid c.$edge-grey
&__details &__details
flex: 1 flex: 1
&__type &__type
font-size: 15px font-size: 15px
color: map.get($_theme, "fg-dim") color: c.$fg-dim
&__remove &__remove
flex-shrink: 0 flex-shrink: 0

View File

@ -1,6 +1,4 @@
$_theme: () !default @use "./colors.sass" as c
@use "sass:map"
.footer__container .footer__container
flex: 1 flex: 1
@ -12,10 +10,9 @@ $_theme: () !default
display: flex display: flex
flex-direction: column flex-direction: column
align-items: center align-items: center
background-color: map.get($_theme, "bg-dim") background-color: c.$bg-darkest
margin: 40px 0 0 margin: 40px 0 0
padding: 10px 10px 30px padding: 10px 10px 30px
border-top: 1px solid map.get($_theme, "edge-grey")
.footer__cols .footer__cols
display: flex display: flex

View File

@ -1,9 +1,7 @@
$_theme: () !default @use "colors.sass" as c
@use "sass:map"
@mixin disabled @mixin disabled
background-color: map.get($_theme, "bg-2") background-color: c.$bg-dark
color: #808080 color: #808080
fieldset fieldset
@ -22,7 +20,7 @@ fieldset
font-size: 28px font-size: 28px
font-weight: bold font-weight: bold
padding: 0 padding: 0
border-bottom: 1px solid map.get($_theme, "edge-grey") // TODO: originally contrasted more border-bottom: 1px solid #333
line-height: 1.56 line-height: 1.56
@media screen and (max-width: 400px) @media screen and (max-width: 400px)
@ -38,7 +36,7 @@ fieldset
position: relative position: relative
padding-bottom: 5px padding-bottom: 5px
margin-bottom: 5px margin-bottom: 5px
border-bottom: 1px solid map.get($_theme, "edge-grey") border-bottom: 1px solid #999
@media screen and (max-width: 400px) @media screen and (max-width: 400px)
flex-direction: column flex-direction: column
@ -54,7 +52,7 @@ fieldset
&__label &__label
grid-area: label grid-area: label
padding: 8px 8px 8px 0px padding: 8px 8px 8px 0px
color: map.get($_theme, "fg-main") color: #fff
&__input &__input
grid-area: input grid-area: input
@ -65,7 +63,7 @@ fieldset
white-space: pre-line white-space: pre-line
margin: 12px 0px 18px margin: 12px 0px 18px
font-size: 16px font-size: 16px
color: map.get($_theme, "fg-dim") color: #ccc
line-height: 1.2 line-height: 1.2
// //
@ -81,7 +79,7 @@ fieldset
width: 16px width: 16px
height: 16px height: 16px
padding: 0px padding: 0px
border: 1px solid map.get($_theme, "edge-grey") border: 1px solid #666
border-radius: 3px border-radius: 3px
margin-left: 8px margin-left: 8px
position: relative position: relative
@ -103,20 +101,6 @@ fieldset
@include acts-like-button @include acts-like-button
cursor: pointer cursor: pointer
.checkbox-hider__container
position: relative
display: grid // why does the default not work???
top: -42px
background: map.get($_theme, "bg-3")
line-height: 1
border: 1px solid map.get($_theme, "edge-grey")
border-radius: 8px
margin-bottom: -18px
.checkbox-hider__label
padding: 12px 0px 12px 32px
cursor: pointer
@mixin checkbox-hider($base) @mixin checkbox-hider($base)
##{$base} ##{$base}
position: relative position: relative
@ -126,17 +110,18 @@ fieldset
height: 42px height: 42px
margin: 0 margin: 0
/*
automatically add these styles too
this means that components based off this can either add the .checkbox-hider__container class, or they can add the .base-name-container class,
depending on which one is more reasonable in the moment
for example, .delete-confirm-container takes advantage of the @extend here.
.#{$base}-container .#{$base}-container
@extend .checkbox-hider__container position: relative
display: grid // why does the default not work???
top: -42px
background: c.$bg-accent-x
line-height: 1
border-radius: 8px
margin-bottom: -18px
.#{$base}-label .#{$base}-label
@extend .checkbox-hider__label padding: 12px 0px 12px 32px
cursor: pointer
@mixin single-button-form @mixin single-button-form
display: inline-block display: inline-block

View File

@ -1,6 +1,4 @@
$_theme: () !default @use "colors.sass" as c
@use "sass:map"
.home-page .home-page
padding: 40px padding: 40px
@ -20,8 +18,8 @@ $_theme: () !default
padding: 16px padding: 16px
border-radius: 4px border-radius: 4px
font-size: 20px font-size: 20px
background-color: map.get($_theme, "bg-1") background-color: c.$bg-darker
color: map.get($_theme, "fg-main") color: c.$fg-main
p p
margin: 0 32px margin: 0 32px

View File

@ -1,5 +1,3 @@
$_theme: () !default
.js-licenses-page .js-licenses-page
max-width: 800px max-width: 800px
margin: 0 auto margin: 0 auto

View File

@ -1,14 +1,12 @@
$_theme: () !default @use "colors.sass" as c
@use "buttons.sass" as *
@use "sass:map" @use "_dimensions.sass" as dimensions
@use "buttons" as *
@use "_dimensions" as dimensions
.main-nav .main-nav
background-color: map.get($_theme, "bg-nav") background-color: c.$bg-accent
display: flex display: flex
padding: 8px padding: 8px
border-bottom: 1px solid map.get($_theme, "edge-grey") box-shadow: 0px 0px 20px 5px rgba(0, 0, 0, 0.1)
+dimensions.thin +dimensions.thin
display: block display: block
@ -32,16 +30,10 @@ $_theme: () !default
font-weight: bold font-weight: bold
&, &:visited &, &:visited
color: map.get($_theme, "fg-bright") color: #fff
&:focus, &:hover &:focus, &:hover
background-color: map.get($_theme, "bg-4") background-color: c.$bg-accent-x
&.icon-link
color: map.get($_theme, "fg-dim")
&:hover, &:focus
color: map.get($_theme, "fg-bright")
.search-form .search-form
display: flex display: flex
@ -52,7 +44,8 @@ $_theme: () !default
@include button-bg @include button-bg
padding: 10px padding: 10px
flex: 1 flex: 1
border: 1px solid map.get($_theme, "bg-nav") margin: 1px
&:hover, &:focus &:hover, &:focus
border-color: map.get($_theme, "edge-grey") border: 1px solid c.$edge-grey
margin: 0px

View File

@ -1,5 +1,3 @@
$_theme: () !default
.privacy-page .privacy-page
max-width: 600px max-width: 600px
margin: 0 auto margin: 0 auto

View File

@ -1,7 +1,5 @@
$_theme: () !default @use "video-list-item.sass" as *
@use "colors.sass" as c
@use "sass:map"
@use "video-list-item" as *
.search-page .search-page
padding: 40px 20px 20px padding: 40px 20px 20px

View File

@ -1,7 +1,5 @@
$_theme: () !default @use "forms.sass" as forms
@use "colors.sass" as c
@use "sass:map"
@use "forms" as forms
.settings-page .settings-page
padding: 40px 20px 20px padding: 40px 20px 20px
@ -21,9 +19,8 @@ $_theme: () !default
.more-settings .more-settings
margin-top: 24px margin-top: 24px
padding: 12px padding: 12px
border: 1px solid map.get($_theme, "edge-grey")
border-radius: 8px border-radius: 8px
background-color: map.get($_theme, "bg-3") background-color: c.$bg-accent-x
&__list &__list
margin: 0 margin: 0
@ -37,7 +34,7 @@ $_theme: () !default
margin-top: 24px margin-top: 24px
.delete-confirm-container .delete-confirm-container
background: map.get($_theme, "bg-1") background: c.$bg-darker
margin-bottom: -36px margin-bottom: -36px
@include forms.checkbox-hider("delete-confirm") @include forms.checkbox-hider("delete-confirm")

View File

@ -1,8 +1,6 @@
$_theme: () !default @use "colors.sass" as c
@use "video-list-item.sass" as *
@use "sass:map" @use "forms.sass" as forms
@use "forms" as forms
@use "video-list-item" as *
.subscriptions-page .subscriptions-page
padding: 40px 20px 20px padding: 40px 20px 20px
@ -35,11 +33,7 @@ $_theme: () !default
.name .name
font-size: 22px font-size: 22px
color: map.get($_theme, "fg-main") color: c.$fg-main
.missing-reason
font-size: 16px
color: map.get($_theme, "fg-warning")
@include forms.checkbox-hider("watched-videos-display") @include forms.checkbox-hider("watched-videos-display")

View File

@ -0,0 +1,14 @@
@use "colors.sass" as c
.takedown-page
max-width: 700px
margin: 0 auto
.new-section
margin-top: 200px
.important-section
padding: 4px 20px
border: 1px solid c.$edge-grey
color: c.$fg-bright
background-color: c.$bg-darker

View File

@ -1,7 +1,5 @@
$_theme: () !default @use "colors.sass" as c
@use "video-list-item.sass" as *
@use "sass:map"
@use "video-list-item" as *
.video-embed-page .video-embed-page
display: block display: block
@ -18,5 +16,5 @@ $_theme: () !default
width: 100% width: 100%
.stream-notice .stream-notice
background: map.get($_theme, "bg-0") background: c.$bg-darkest
padding: 4px padding: 4px

View File

@ -1,7 +1,5 @@
$_theme: () !default @use "colors.sass" as c
@use "_dimensions.sass" as dimensions
@use "sass:map"
@use "_dimensions" as dimensions
// navigator hacks // navigator hacks
.thumbnail > .thumbnail__options-container .thumbnail > .thumbnail__options-container
@ -32,7 +30,6 @@ $_theme: () !default
&__show-more &__show-more
display: block display: block
height: $more-size height: $more-size
color: #fff
line-height: 16px line-height: 16px
font-size: 25px font-size: 25px
text-align: center text-align: center
@ -55,7 +52,7 @@ $_theme: () !default
&__options-list &__options-list
pointer-events: auto pointer-events: auto
display: grid display: grid
background-color: map.get($_theme, "bg-3") background-color: c.$bg-accent
padding: 8px 0px padding: 8px 0px
border-radius: 8px border-radius: 8px
box-shadow: 0 2px 6px 2px #000 box-shadow: 0 2px 6px 2px #000
@ -70,7 +67,7 @@ $_theme: () !default
right: 0 right: 0
transform: translate(-6px, -1px) rotate(-45deg) transform: translate(-6px, -1px) rotate(-45deg)
clip-path: polygon(-5% -20%, 120% -20%, 120% 125%) clip-path: polygon(-5% -20%, 120% -20%, 120% 125%)
background-color: map.get($_theme, "bg-3") background-color: c.$bg-accent
box-shadow: 0px 0px 4px 0px #000 box-shadow: 0px 0px 4px 0px #000
pointer-events: none pointer-events: none
@ -83,7 +80,7 @@ $_theme: () !default
margin-bottom: 12px margin-bottom: 12px
@at-root .video-list-item--watched#{&} @at-root .video-list-item--watched#{&}
background: map.get($_theme, "bg-dim") background: c.$bg-darker
padding: 4px 4px 0px padding: 4px 4px 0px
margin: -4px -4px 8px margin: -4px -4px 8px
@ -96,7 +93,7 @@ $_theme: () !default
.thumbnail .thumbnail
position: relative position: relative
display: flex display: flex
background: map.get($_theme, "bg-0") background: c.$bg-darkest
&__link &__link
font-size: 0 // remove whitespace around the image font-size: 0 // remove whitespace around the image
@ -109,7 +106,7 @@ $_theme: () !default
position: absolute position: absolute
bottom: 3px bottom: 3px
right: 3px right: 3px
color: #fff color: c.$fg-bright
font-size: 14px font-size: 14px
background: rgba(20, 20, 20, 0.85) background: rgba(20, 20, 20, 0.85)
line-height: 1 line-height: 1
@ -122,20 +119,20 @@ $_theme: () !default
line-height: 1.2 line-height: 1.2
.title-link .title-link
color: map.get($_theme, "fg-main") color: c.$fg-main
text-decoration: none text-decoration: none
.author-line .author-line
margin-top: 4px margin-top: 4px
font-size: 15px font-size: 15px
color: map.get($_theme, "fg-dim") color: c.$fg-dim
.author .author
color: map.get($_theme, "fg-dim") color: c.$fg-dim
text-decoration: none text-decoration: none
&:hover, &:active &:hover, &:active
color: map.get($_theme, "fg-bright") color: c.$fg-bright
text-decoration: underline text-decoration: underline
@mixin recommendation-item @mixin recommendation-item
@ -179,15 +176,15 @@ $_theme: () !default
.author-line .author-line
font-size: 15px font-size: 15px
color: map.get($_theme, "fg-main") color: c.$fg-main
.author .author
color: map.get($_theme, "fg-main") color: c.$fg-main
.description .description
margin-top: 16px margin-top: 16px
font-size: 15px font-size: 15px
color: map.get($_theme, "fg-dim") color: c.$fg-dim
+dimensions.thin +dimensions.thin
.description .description
@ -198,7 +195,7 @@ $_theme: () !default
.description b .description b
font-weight: normal font-weight: normal
color: map.get($_theme, "fg-main") color: c.$fg-main
@mixin channel-video @mixin channel-video
@include large-item @include large-item

View File

@ -1,11 +1,11 @@
$_theme: () !default @use "colors.sass" as c
@use "video-list-item.sass" as *
@use "sass:map"
@use "video-list-item" as *
.video-page .video-page
display: grid display: grid
grid-auto-flow: row grid-auto-flow: row
padding: 20px
grid-gap: 16px
@media screen and (min-width: 1000px) @media screen and (min-width: 1000px)
grid-template-columns: 1fr 400px grid-template-columns: 1fr 400px
@ -13,25 +13,10 @@ $_theme: () !default
&--recommended-below, &--recommended-hidden &--recommended-below, &--recommended-hidden
grid-template-columns: none grid-template-columns: none
&--recommended-side &--recommended-hidden .related-videos
.related-videos display: none
border-left: 1px solid map.get($_theme, "edge-grey")
padding-left: 12px
padding-right: 20px
background-color: map.get($_theme, "bg-4")
padding-top: 12px
&--recommended-below
.related-videos
padding: 20px
&--recommended-hidden
.related-videos
display: none
.main-video-section .main-video-section
padding: 20px
.video-container .video-container
text-align: center text-align: center
@ -42,7 +27,7 @@ $_theme: () !default
max-height: 80vh max-height: 80vh
.stream-notice .stream-notice
background: map.get($_theme, "bg-0") background: c.$bg-darkest
padding: 4px padding: 4px
.info .info
@ -60,15 +45,15 @@ $_theme: () !default
margin: 0px 0px 4px margin: 0px 0px 4px
font-size: 30px font-size: 30px
font-weight: normal font-weight: normal
color: map.get($_theme, "fg-bright") color: c.$fg-bright
word-break: break-word word-break: break-word
.author-link .author-link
color: map.get($_theme, "fg-main") color: c.$fg-main
text-decoration: none text-decoration: none
&:hover, &:active &:hover, &:active
color: map.get($_theme, "fg-bright") color: c.$fg-bright
text-decoration: underline text-decoration: underline
.info-secondary .info-secondary
@ -90,7 +75,7 @@ $_theme: () !default
margin: 16px 4px margin: 16px 4px
padding: 12px padding: 12px
border-radius: 4px border-radius: 4px
background-color: map.get($_theme, "bg-0") background-color: c.$bg-darkest
&__description &__description
margin-left: 12px margin-left: 12px
@ -100,7 +85,7 @@ $_theme: () !default
&__script-warning &__script-warning
font-size: 15px font-size: 15px
color: map.get($_theme, "fg-warning") color: c.$fg-warning
&__buttons &__buttons
display: flex display: flex
@ -112,12 +97,12 @@ $_theme: () !default
line-height: 1.4 line-height: 1.4
word-break: break-word word-break: break-word
margin: 16px 4px 4px 4px margin: 16px 4px 4px 4px
background-color: map.get($_theme, "bg-5") background-color: c.$bg-accent-area
padding: 12px padding: 12px
border-radius: 4px border-radius: 4px
--regular-background: #{map.get($_theme, "bg-5")} --regular-background: #{c.$bg-accent-area}
--highlight-background: #{map.get($_theme, "bg-1")} --highlight-background: #{c.$bg-darker}
.subscribe-form .subscribe-form
display: inline-block display: inline-block

View File

@ -1,2 +0,0 @@
@use "themes/light" as *
@use "includes/main" with ($_theme: $theme)

31
sass/main.sass Normal file
View File

@ -0,0 +1,31 @@
@use "includes/colors.sass" as c
@use "includes/base.sass"
@use "sass:selector"
@use "includes/video-page.sass"
@use "includes/video-embed.sass"
@use "includes/search-page.sass"
@use "includes/home-page.sass"
@use "includes/channel-page.sass"
@use "includes/subscriptions-page.sass"
@use "includes/settings-page.sass"
@use "includes/cant-think-page.sass"
@use "includes/privacy-page.sass"
@use "includes/licenses-page.sass"
@use "includes/filters-page.sass"
@use "includes/takedown-page.sass"
@use "includes/forms.sass"
@use "includes/nav.sass"
@use "includes/footer.sass"
@font-face
font-family: "Bariol"
src: url(/static/fonts/bariol.woff?statichash=1)
.icon-link:hover, .icon-link:focus
.icon
filter: brightness(2)
.button-container
display: flex
flex-wrap: wrap

View File

@ -1,27 +0,0 @@
$_theme: () !default
@use "sass:map"
// remove separating edges
.main-nav, .footer__center, .video-page--recommended-side .related-videos
border: none
// no background change to recommended videos sidebar
.video-page--recommended-side .related-videos
background-color: map.get($_theme, "bg-2")
// navigation shadow
.main-nav
position: relative // needed for box shadow to overlap related videos section
box-shadow: 0px 0px 20px 5px rgba(0, 0, 0, 0.1)
// thumbnail dropdown menu dividers
.menu-look
border-color: map.get($_theme, "bg-0")
// details areas
details, .checkbox-hider__container, .more-settings
border: none
details[open] summary
border: none
margin-bottom: 4px

View File

@ -1,38 +0,0 @@
// Defined in scss file instead of sass because indented syntax does not have multiline maps
// https://github.com/sass/sass/issues/216
@use "sass:map";
// This section is for colour shades
$theme: (
// darker
"bg-0": #252628,
"bg-1": #303336,
// regular
"bg-2": #36393f,
// lighter
"bg-3": #3f4247, // slightly
"bg-4": #44474b, // noticably
"bg-5": #4f5359, // brightly
"fg-bright": #fff,
"fg-main": #ddd,
"fg-dim": #bbb,
"fg-warning": #fdca6d,
"edge-grey": #a0a0a0,
"placeholder": #c4c4c4,
"link": #8ac2f9,
"power-deep": #c62727,
"power-fg": "#fff",
"image-dropdown": url(/static/images/arrow-down-wide-dark.svg)
);
// This section is for colour meanings
$theme: map.merge($theme, (
"bg-dim": map.get($theme, "bg-0"),
"bg-nav": map.get($theme, "bg-5"),
));

View File

@ -1,8 +0,0 @@
// extend regular light theme to change a couple of shades
@use "light";
@use "sass:map";
// this section is for colour meanings
$theme: map.merge(light.$theme, (
"edge-grey": #c0c0c0,
));

View File

@ -1,38 +0,0 @@
// Defined in scss file instead of sass because indented syntax does not have multiline maps
// https://github.com/sass/sass/issues/216
@use "sass:map";
// this section is for colour shades
$theme: (
// lighter
"bg-0": #fff,
"bg-1": #fff,
// regular
"bg-2": #f2f2f2,
// darker
"bg-3": #e8e8e8, // slightly
"bg-4": #dadada, // noticably
"bg-5": #d0d0d0, // brightly
"fg-bright": #000,
"fg-main": #202020,
"fg-dim": #454545,
"fg-warning": #ce8600,
"edge-grey": #909090,
"placeholder": #636363,
"link": #0b51d4,
"power-deep": #c62727,
"power-fg": #fff,
"image-dropdown": url(/static/images/arrow-down-wide-light.svg)
);
// this section is for colour meanings
$theme: map.merge($theme, (
"bg-dim": map.get($theme, "bg-4"),
"bg-nav": map.get($theme, "bg-0")
));

View File

@ -1,11 +1,9 @@
const {Pinski} = require("pinski") const {Pinski} = require("pinski")
const {setInstance} = require("pinski/plugins") const {setInstance} = require("pinski/plugins")
const constants = require("./utils/constants") const constants = require("./utils/constants")
const iconLoader = require("./utils/icon-loader").icons
;(async () => { ;(async () => {
await require("./utils/upgradedb")() await require("./utils/upgradedb")()
const icons = await iconLoader
const server = new Pinski({ const server = new Pinski({
port: 10412, port: 10412,
@ -15,19 +13,19 @@ const iconLoader = require("./utils/icon-loader").icons
setInstance(server) setInstance(server)
server.pugDefaultLocals.constants = constants server.pugDefaultLocals.constants = constants
server.pugDefaultLocals.icons = icons
server.muteLogsStartingWith("/vi/") server.muteLogsStartingWith("/vi/")
server.muteLogsStartingWith("/favicon") server.muteLogsStartingWith("/favicon")
server.muteLogsStartingWith("/static") server.muteLogsStartingWith("/static")
server.addSassDir("sass", ["sass/includes", "sass/themes", "sass/theme-modules"]) server.addSassDir("sass", ["sass/includes"])
server.addRoute("/static/css/dark.css", "sass/dark.sass", "sass") server.addRoute("/static/css/main.css", "sass/main.sass", "sass")
server.addRoute("/static/css/light.css", "sass/light.sass", "sass")
server.addRoute("/static/css/edgeless-light.css", "sass/edgeless-light.sass", "sass")
server.addPugDir("pug", ["pug/includes"]) server.addPugDir("pug", ["pug/includes"])
server.addPugDir("pug/errors") server.addPugDir("pug/errors")
server.addRoute("/cant-think", "pug/cant-think.pug", "pug")
server.addRoute("/privacy", "pug/privacy.pug", "pug")
server.addRoute("/licenses", "pug/licenses.pug", "pug")
server.addStaticHashTableDir("html/static/js") server.addStaticHashTableDir("html/static/js")
server.addStaticHashTableDir("html/static/js/elemjs") server.addStaticHashTableDir("html/static/js/elemjs")

View File

@ -9,10 +9,6 @@ let constants = {
type: "string", type: "string",
default: "http://localhost:3000" default: "http://localhost:3000"
}, },
theme: {
type: "integer",
default: 0
},
save_history: { save_history: {
type: "boolean", type: "boolean",
default: false default: false
@ -50,7 +46,6 @@ let constants = {
csrf_time: 4*60*60*1000, csrf_time: 4*60*60*1000,
seen_token_subscriptions_eligible: 40*60*60*1000, seen_token_subscriptions_eligible: 40*60*60*1000,
subscriptions_refresh_loop_min: 5*60*1000, subscriptions_refresh_loop_min: 5*60*1000,
subscriptions_refesh_fake_not_found_cooldown: 10*60*1000,
}, },
// Pattern matching. // Pattern matching.

View File

@ -54,7 +54,7 @@ class User {
getSubscriptions() { getSubscriptions() {
if (this.token) { if (this.token) {
return db.prepare("SELECT ucid FROM Subscriptions WHERE token = ?").pluck().all(this.token) return db.prepare("SELECT ucid FROM Subscriptions WHERE token = ? AND channel_missing = 0").pluck().all(this.token)
} else { } else {
return [] return []
} }
@ -62,7 +62,7 @@ class User {
isSubscribed(ucid) { isSubscribed(ucid) {
if (this.token) { if (this.token) {
return !!db.prepare("SELECT * FROM Subscriptions WHERE token = ? AND ucid = ?").get([this.token, ucid]) return !!db.prepare("SELECT * FROM Subscriptions WHERE token = ? AND ucid = ? AND channel_missing = 0").get([this.token, ucid])
} else { } else {
return false return false
} }

View File

@ -1,8 +0,0 @@
const fs = require("fs").promises
const names = ["subscriptions", "settings"]
const icons = names.map(name => fs.readFile(`html/static/images/${name}.svg`, "utf8"))
module.exports.icons = Promise.all(icons).then(resolvedIcons => {
return new Map(names.map((name, index) => [name, resolvedIcons[index]]))
})

View File

@ -70,31 +70,6 @@ const deltas = [
.run() .run()
db.prepare("CREATE TABLE TakedownChannels (ucid TEXT NOT NULL, org TEXT, url TEXT, PRIMARY KEY (ucid))") db.prepare("CREATE TABLE TakedownChannels (ucid TEXT NOT NULL, org TEXT, url TEXT, PRIMARY KEY (ucid))")
.run() .run()
},
// 11: Settings +theme
function() {
db.prepare("ALTER TABLE Settings ADD COLUMN theme INTEGER DEFAULT 0")
.run()
},
// 12: Channels +missing +missing_reason, Subscriptions -
// Better management for missing channels
// We totally discard the existing Subscriptions.channel_missing since it is unreliable.
function() {
db.prepare("ALTER TABLE Channels ADD COLUMN missing INTEGER NOT NULL DEFAULT 0")
.run()
db.prepare("ALTER TABLE Channels ADD COLUMN missing_reason TEXT")
.run()
// https://www.sqlite.org/lang_altertable.html#making_other_kinds_of_table_schema_changes
db.transaction(() => {
db.prepare("CREATE TABLE NEW_Subscriptions (token TEXT NOT NULL, ucid TEXT NOT NULL, PRIMARY KEY (token, ucid))")
.run()
db.prepare("INSERT INTO NEW_Subscriptions (token, ucid) SELECT token, ucid FROM Subscriptions")
.run()
db.prepare("DROP TABLE Subscriptions")
.run()
db.prepare("ALTER TABLE NEW_Subscriptions RENAME TO Subscriptions")
.run()
})()
} }
] ]
@ -107,7 +82,7 @@ async function createBackup(entry) {
/** /**
* @param {number} entry * @param {number} entry
* @param {boolean} [log] * @param {boolean} log
*/ */
function runDelta(entry, log) { function runDelta(entry, log) {
process.stdout.write(`Upgrading database to version ${entry}... `) process.stdout.write(`Upgrading database to version ${entry}... `)

View File

@ -1,59 +1,15 @@
const {request} = require("./request") const {request} = require("./request")
const db = require("./db") const db = require("./db")
async function fetchChannel(path, ucid, instance) { async function fetchChannel(ucid, instance) {
function updateGoodData(channel) {
const bestIcon = channel.authorThumbnails.slice(-1)[0]
const iconURL = bestIcon ? bestIcon.url : null
db.prepare("REPLACE INTO Channels (ucid, name, icon_url, missing, missing_reason) VALUES (?, ?, ?, 0, NULL)").run(channel.authorId, channel.author, iconURL)
}
function updateBadData(channel) {
if (channel.identifier === "NOT_FOUND" || channel.identifier === "ACCOUNT_TERMINATED") {
db.prepare("UPDATE Channels SET missing = 1, missing_reason = ? WHERE ucid = ?").run(channel.error, channel.authorId)
return {
missing: true,
message: channel.error
}
} else {
return {
missing: false,
message: channel.error
}
}
}
if (!instance) throw new Error("No instance parameter provided") if (!instance) throw new Error("No instance parameter provided")
// fetch
const row = db.prepare("SELECT * FROM Channels WHERE ucid = ?").get(ucid) const channel = await request(`${instance}/api/v1/channels/${ucid}`).then(res => res.json())
// update database
// handle the case where the channel has a known error const bestIcon = channel.authorThumbnails.slice(-1)[0]
if (row && row.missing_reason) { const iconURL = bestIcon ? bestIcon.url : null
return { db.prepare("REPLACE INTO Channels (ucid, name, icon_url) VALUES (?, ?, ?)").run([channel.authorId, channel.author, iconURL])
error: true, // return
ucid,
row,
missing: true,
message: row.missing_reason
}
}
/** @type {any} */
const channel = await request(`${instance}/api/v1/channels/${ucid}?second__path=${path}`).then(res => res.json())
// handle the case where the channel has a newly discovered error
if (channel.error) {
const missingData = updateBadData(channel)
return {
error: true,
ucid,
row,
...missingData
}
}
// handle the case where the channel returns good data (this is the only remaining scenario)
updateGoodData(channel)
return channel return channel
} }