1
0
mirror of https://git.sr.ht/~cadence/cloudtube synced 2024-11-10 02:27:29 +00:00

Add database and subscribe button

This commit is contained in:
Cadence Ember 2020-08-31 01:54:59 +12:00
parent 2af05e43a9
commit f24e1bb39c
No known key found for this signature in database
GPG Key ID: 128B99B1B74A6412
25 changed files with 972 additions and 44 deletions

1
.gitignore vendored
View File

@ -6,3 +6,4 @@
# Auto-generated files # Auto-generated files
node_modules node_modules
/db

View File

@ -1,12 +1,16 @@
const fetch = require("node-fetch")
const {render} = require("pinski/plugins") const {render} = require("pinski/plugins")
const constants = require("./utils/constants")
const {fetchChannel} = require("./utils/youtube")
const {getUser} = require("./utils/getuser")
module.exports = [ module.exports = [
{ {
route: "/channel/([A-Za-z0-9-_]+)", methods: ["GET"], code: async ({fill}) => { route: `/channel/(${constants.regex.ucid})`, methods: ["GET"], code: async ({req, fill}) => {
const id = fill[0] const id = fill[0]
const data = await fetch(`http://localhost:3000/api/v1/channels/${id}`).then(res => res.json()) const data = await fetchChannel(id)
return render(200, "pug/channel.pug", {data}) const user = getUser(req)
const subscribed = user.isSubscribed(id)
return render(200, "pug/channel.pug", {data, subscribed})
} }
} }
] ]

56
api/formapi.js Normal file
View File

@ -0,0 +1,56 @@
const {redirect} = require("pinski/plugins")
const db = require("./utils/db")
const constants = require("./utils/constants")
const {getToken} = require("./utils/getuser")
const validate = require("./utils/validate")
const V = validate.V
const {fetchChannel} = require("./utils/youtube")
module.exports = [
{
route: `/formapi/(un|)subscribe/(${constants.regex.ucid})`, methods: ["POST"], upload: true, code: async ({req, fill, body}) => {
const add = !fill[0]
const ucid = fill[1]
return new V()
.with(validate.presetLoad({body}))
.with(validate.presetURLParamsBody())
.last(async state => {
const {params} = state
const responseHeaders = {}
const token = getToken(req, responseHeaders)
if (add) {
await fetchChannel(ucid)
db.prepare("INSERT OR IGNORE INTO Subscriptions (token, ucid) VALUES (?, ?)").run(token, ucid)
} else {
db.prepare("DELETE FROM Subscriptions WHERE token = ? AND ucid = ?").run(token, ucid)
}
if (params.has("referrer")) {
return {
statusCode: 303,
contentType: "application/json",
headers: Object.assign(responseHeaders, {
Location: params.get("referrer")
}),
content: {
status: "ok"
}
}
return redirect(params.get("referrer"), 303)
} else {
return {
statusCode: 200,
contentType: "application/json",
headers: responseHeaders,
content: {
status: "ok"
}
}
}
})
.go()
}
}
]

12
api/utils/constants.js Normal file
View File

@ -0,0 +1,12 @@
const constants = {
caching: {
csrf_time: 4*60*60*1000
},
regex: {
ucid: "[A-Za-z0-9-_]+",
video_id: "[A-Za-z0-9-_]+"
}
}
module.exports = constants

8
api/utils/db.js Normal file
View File

@ -0,0 +1,8 @@
const sqlite = require("better-sqlite3")
const pj = require("path").join
const fs = require("fs")
const dir = pj(__dirname, "../../db")
fs.mkdirSync(pj(dir, "backups"), {recursive: true})
const db = new sqlite(pj(dir, "cloudtube.db"))
module.exports = db

78
api/utils/getuser.js Normal file
View File

@ -0,0 +1,78 @@
const crypto = require("crypto")
const {parse: parseCookie} = require("cookie")
const constants = require("./constants")
const db = require("./db")
function getToken(req, responseHeaders) {
if (!req.headers.cookie) req.headers.cookie = ""
const cookie = parseCookie(req.headers.cookie)
const token = cookie.token
if (token) return token
if (responseHeaders) { // we should create a token
const setCookie = responseHeaders["set-cookie"] || []
const token = crypto.randomBytes(18).toString("base64").replace(/\W/g, "_")
setCookie.push(`token=${token}; Path=/; Max-Age=2147483648; HttpOnly; SameSite=Lax`)
responseHeaders["set-cookie"] = setCookie
return token
}
return null
}
class User {
constructor(token) {
this.token = token
}
getSubscriptions() {
if (this.token) {
return db.prepare("SELECT ucid FROM Subscriptions WHERE token = ?").pluck().all(ucid)
} else {
return []
}
}
isSubscribed(ucid) {
if (this.token) {
return !!db.prepare("SELECT * FROM Subscriptions WHERE token = ? AND ucid = ?").get([this.token, ucid])
} else {
return false
}
}
}
/**
* @param {any} responseHeaders supply this to create a token
*/
function getUser(req, responseHeaders) {
const token = getToken(req, responseHeaders)
return new User(token)
}
function generateCSRF() {
const token = crypto.randomBytes(16).toString("hex")
const expires = Date.now() + constants.caching.csrf_time
db.prepare("INSERT INTO CSRFTokens (token, expires) VALUES (?, ?)").run(token, expires)
return token
}
function checkCSRF(token) {
const row = db.prepare("SELECT * FROM CSRFTokens WHERE token = ? AND expires > ?").get(token, Date.now())
if (row) {
db.prepare("DELETE FROM CSRFTokens WHERE token = ?").run(token)
return true
} else {
return false
}
}
function cleanCSRF() {
db.prepare("DELETE FROM CSRFTokens WHERE expires <= ?").run(Date.now())
}
cleanCSRF()
setInterval(cleanCSRF, constants.caching.csrf_time).unref()
module.exports.getToken = getToken
module.exports.generateCSRF = generateCSRF
module.exports.checkCSRF = checkCSRF
module.exports.getUser = getUser

55
api/utils/upgradedb.js Normal file
View File

@ -0,0 +1,55 @@
const pj = require("path").join
const db = require("./db")
const deltas = [
// 0: from empty file, +DatabaseVersion, +Subscriptions
function() {
db.prepare("CREATE TABLE DatabaseVersion (version INTEGER NOT NULL, PRIMARY KEY (version))")
.run()
db.prepare("CREATE TABLE Subscriptions (token TEXT NOT NULL, ucid TEXT NOT NULL, PRIMARY KEY (token, ucid))")
.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 CSRFTokens (token TEXT NOT NULL, expires INTEGER NOT NULL, PRIMARY KEY (token))")
.run()
}
]
async function createBackup(entry) {
const filename = `db/backups/cloudtube.db.bak-v${entry-1}`
process.stdout.write(`Backing up current to ${filename}... `)
await db.backup(pj(__dirname, "../../", filename))
process.stdout.write("done.\n")
}
/**
* @param {number} entry
* @param {boolean} log
*/
function runDelta(entry, log) {
process.stdout.write(`Upgrading database to version ${entry}... `)
deltas[entry]()
db.prepare("DELETE FROM DatabaseVersion").run()
db.prepare("INSERT INTO DatabaseVersion (version) VALUES (?)").run(entry)
process.stdout.write("done.\n")
}
module.exports = async function() {
let currentVersion = -1
const newVersion = deltas.length - 1
try {
currentVersion = db.prepare("SELECT version FROM DatabaseVersion").pluck().get()
} catch (e) {} // if the table doesn't exist yet then we don't care
if (currentVersion !== newVersion) {
// go through the entire upgrade sequence
for (let entry = currentVersion+1; entry <= newVersion; entry++) {
// Back up current version
if (entry > 0) await createBackup(entry)
// Run delta
runDelta(entry)
}
}
}

83
api/utils/validate.js Normal file
View File

@ -0,0 +1,83 @@
class V {
constructor() {
this.chain = []
this.state = {}
this.finished = false
this.endValue = null
}
with(preset) {
this.check(...preset)
return this
}
check(conditionCallback, elseCallback) {
this.chain.push(() => {
if (!conditionCallback(this.state)) this._end(elseCallback(this.state))
})
return this
}
last(callback) {
this.chain.push(() => {
this._end(callback(this.state))
})
return this
}
go() {
for (const s of this.chain) {
s()
if (this.finished) return this.endValue
}
return {
statusCode: 500,
contentType: "application/json",
content: {
error: "Reached end of V chain without response"
}
}
}
_end(value) {
this.finished = true
this.endValue = value
}
}
function presetLoad(additions) {
return [
state => {
Object.assign(state, additions)
return true
},
null
]
}
function presetURLParamsBody() {
return [
state => {
try {
state.params = new URLSearchParams(state.body.toString())
return true
} catch (e) {
console.error(e)
return false
}
},
() => {
return {
statusCode: 400,
contentType: "application/json",
content: {
error: "Could not parse body as URLSearchParams"
}
}
}
]
}
module.exports.V = V
module.exports.presetLoad = presetLoad
module.exports.presetURLParamsBody = presetURLParamsBody

15
api/utils/youtube.js Normal file
View File

@ -0,0 +1,15 @@
const fetch = require("node-fetch")
const db = require("./db")
async function fetchChannel(ucid) {
// fetch
const channel = await fetch(`http://localhost:3000/api/v1/channels/${ucid}`).then(res => res.json())
// update database
const bestIcon = channel.authorThumbnails.slice(-1)[0]
const iconURL = bestIcon ? bestIcon.url : null
db.prepare("REPLACE INTO Channels (ucid, name, icon_url) VALUES (?, ?, ?)").run([channel.authorId, channel.author, iconURL])
// return
return channel
}
module.exports.fetchChannel = fetchChannel

19
api/video.js Normal file
View File

@ -0,0 +1,19 @@
const fetch = require("node-fetch")
const {render} = require("pinski/plugins")
const db = require("./utils/db")
const {getToken} = require("./utils/getuser")
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])
}
return render(200, "pug/video.pug", {video, subscribed})
}
}
]

View File

@ -177,14 +177,14 @@ function fetchChannel(channelID, ignoreCache) {
} }
module.exports = [ module.exports = [
{ /*{
route: "/watch", methods: ["GET"], code: async ({url}) => { route: "/watch", methods: ["GET"], code: async ({url}) => {
const id = url.searchParams.get("v") const id = url.searchParams.get("v")
const video = await videoCache.getAs(id, () => fetch(`http://localhost:3000/api/v1/videos/${id}`).then(res => res.json())) const video = await videoCache.getAs(id, () => fetch(`http://localhost:3000/api/v1/videos/${id}`).then(res => res.json()))
return render(200, "pug/video.pug", {video}) return render(200, "pug/video.pug", {video})
} }
} },
/*
{ {
route: "/v/(.*)", methods: ["GET"], code: async ({fill}) => { route: "/v/(.*)", methods: ["GET"], code: async ({fill}) => {
let id; let id;

View File

@ -0,0 +1,4 @@
import {q} from "/static/js/elemjs/elemjs.js"
import {SubscribeButton} from "/static/js/subscribe.js"
new SubscribeButton(q("#subscribe"))

View File

@ -1,3 +1,5 @@
document.body.classList.remove("show-focus")
document.addEventListener("mousedown", () => { document.addEventListener("mousedown", () => {
document.body.classList.remove("show-focus") document.body.classList.remove("show-focus")
}) })

View File

@ -1,4 +1,5 @@
import {q, ElemJS} from "/static/js/elemjs/elemjs.js" import {q, ElemJS} from "/static/js/elemjs/elemjs.js"
import {SubscribeButton} from "/static/js/subscribe.js"
const video = q("#video") const video = q("#video")
const audio = q("#audio") const audio = q("#audio")
@ -67,6 +68,7 @@ class QualitySelect extends ElemJS {
onInput() { onInput() {
const itag = this.element.value const itag = this.element.value
formatLoader.play(itag) formatLoader.play(itag)
video.focus()
} }
} }
@ -118,3 +120,40 @@ for (let eventName of ["canplaythrough", "waiting", "stalled"]) {
video.addEventListener(eventName, playbackIntervention) video.addEventListener(eventName, playbackIntervention)
audio.addEventListener(eventName, playbackIntervention) audio.addEventListener(eventName, playbackIntervention)
} }
function relativeSeek(seconds) {
video.currentTime += seconds
}
function togglePlaying() {
if (video.paused) video.play()
else video.pause()
}
document.addEventListener("keydown", event => {
if (["INPUT", "SELECT", "BUTTON"].includes(event.target.tagName)) return
let caught = true
if (event.key === "j" || event.key === "n") {
relativeSeek(-10)
} else if (["k", "p", " ", "e"].includes(event.key)) {
togglePlaying()
} else if (event.key === "l" || event.key === "o") {
relativeSeek(10)
} else if (event.key === "ArrowLeft") {
relativeSeek(-5)
} else if (event.key === "ArrowRight") {
relativeSeek(5)
} else if (event.key === "ArrowUp" || event.key === "ArrowDown") {
// no-op
} else if (event.key >= "0" && event.key <= "9") {
video.currentTime = video.duration * (+event.key) / 10
} else if (event.key === "f") {
if (document.fullscreen) document.exitFullscreen()
else video.requestFullscreen()
} else {
caught = false
}
if (caught) event.preventDefault()
})
new SubscribeButton(q("#subscribe"))

View File

@ -0,0 +1,28 @@
import {ElemJS} from "/static/js/elemjs/elemjs.js"
class SubscribeButton extends ElemJS {
constructor(element) {
super(element)
this.subscribed = this.element.getAttribute("data-subscribed") === "1"
this.ucid = this.element.getAttribute("data-ucid")
this.on("click", this.onClick.bind(this))
this.render()
}
onClick(event) {
event.preventDefault()
this.subscribed = !this.subscribed
const path = this.subscribed ? "subscribe" : "unsubscribe"
fetch(`/formapi/${path}/${this.ucid}`, {method: "POST"})
this.render()
}
render() {
if (!this.subscribed) this.text("Subscribe")
else this.text("Unsubscribe")
}
}
export {
SubscribeButton
}

503
package-lock.json generated
View File

@ -29,6 +29,11 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz",
"integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==" "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w=="
}, },
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
},
"anymatch": { "anymatch": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
@ -38,6 +43,20 @@
"picomatch": "^2.0.4" "picomatch": "^2.0.4"
} }
}, },
"aproba": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
},
"are-we-there-yet": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
"integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
"requires": {
"delegates": "^1.0.0",
"readable-stream": "^2.0.6"
}
},
"asap": { "asap": {
"version": "2.0.6", "version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
@ -56,11 +75,56 @@
"@babel/types": "^7.9.6" "@babel/types": "^7.9.6"
} }
}, },
"base64-js": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
},
"better-sqlite3": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-7.1.0.tgz",
"integrity": "sha512-FV/snQ8F/kyqhdxsevzbojVtMowDWOfe1A5N3lYu1KJwoho2t7JgITmdlSc7DkOh3Zq65I+ZyeNWXQrkLEDFTg==",
"requires": {
"bindings": "^1.5.0",
"prebuild-install": "^5.3.3",
"tar": "4.4.10"
}
},
"binary-extensions": { "binary-extensions": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz",
"integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==" "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ=="
}, },
"bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"requires": {
"file-uri-to-path": "1.0.0"
}
},
"bl": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz",
"integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==",
"requires": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
},
"dependencies": {
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
}
}
},
"braces": { "braces": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
@ -69,6 +133,15 @@
"fill-range": "^7.0.1" "fill-range": "^7.0.1"
} }
}, },
"buffer": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz",
"integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==",
"requires": {
"base64-js": "^1.0.2",
"ieee754": "^1.1.4"
}
},
"character-parser": { "character-parser": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz",
@ -92,6 +165,21 @@
"readdirp": "~3.4.0" "readdirp": "~3.4.0"
} }
}, },
"chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
},
"code-point-at": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
},
"console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
},
"constantinople": { "constantinople": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz",
@ -101,11 +189,62 @@
"@babel/types": "^7.6.1" "@babel/types": "^7.6.1"
} }
}, },
"cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA=="
},
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"decompress-response": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
"integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
"requires": {
"mimic-response": "^2.0.0"
}
},
"deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
},
"delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
},
"detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
},
"doctypes": { "doctypes": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz",
"integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=" "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk="
}, },
"end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
"requires": {
"once": "^1.4.0"
}
},
"expand-template": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="
},
"file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
},
"fill-range": { "fill-range": {
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@ -114,12 +253,45 @@
"to-regex-range": "^5.0.1" "to-regex-range": "^5.0.1"
} }
}, },
"fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
},
"fs-minipass": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz",
"integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
"requires": {
"minipass": "^2.6.0"
}
},
"fsevents": { "fsevents": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
"integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
"optional": true "optional": true
}, },
"gauge": {
"version": "2.7.4",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
"requires": {
"aproba": "^1.0.3",
"console-control-strings": "^1.0.0",
"has-unicode": "^2.0.0",
"object-assign": "^4.1.0",
"signal-exit": "^3.0.0",
"string-width": "^1.0.1",
"strip-ansi": "^3.0.1",
"wide-align": "^1.1.0"
}
},
"github-from-package": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
"integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4="
},
"glob-parent": { "glob-parent": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
@ -133,6 +305,26 @@
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
"integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg=="
}, },
"has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
},
"ieee754": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"ini": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
},
"is-binary-path": { "is-binary-path": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@ -155,6 +347,14 @@
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI="
}, },
"is-fullwidth-code-point": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"requires": {
"number-is-nan": "^1.0.0"
}
},
"is-glob": { "is-glob": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
@ -181,6 +381,11 @@
"has-symbols": "^1.0.1" "has-symbols": "^1.0.1"
} }
}, },
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"js-stringify": { "js-stringify": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz",
@ -205,21 +410,103 @@
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz",
"integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA=="
}, },
"mimic-response": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
"integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA=="
},
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
},
"minipass": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
"integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
}
},
"minizlib": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz",
"integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
"requires": {
"minipass": "^2.9.0"
}
},
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"requires": {
"minimist": "^1.2.5"
}
},
"mkdirp-classic": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
},
"napi-build-utils": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
"integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg=="
},
"node-abi": {
"version": "2.19.1",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.19.1.tgz",
"integrity": "sha512-HbtmIuByq44yhAzK7b9j/FelKlHYISKQn0mtvcBrU5QBkhoCMp5bu8Hv5AI34DcKfOAcJBcOEMwLlwO62FFu9A==",
"requires": {
"semver": "^5.4.1"
}
},
"node-fetch": { "node-fetch": {
"version": "2.6.0", "version": "2.6.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
"integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
}, },
"noop-logger": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz",
"integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI="
},
"normalize-path": { "normalize-path": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
}, },
"npmlog": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
"requires": {
"are-we-there-yet": "~1.1.2",
"console-control-strings": "~1.1.0",
"gauge": "~2.7.3",
"set-blocking": "~2.0.0"
}
},
"number-is-nan": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
},
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
}, },
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"requires": {
"wrappy": "1"
}
},
"path-parse": { "path-parse": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
@ -231,8 +518,8 @@
"integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg=="
}, },
"pinski": { "pinski": {
"version": "git+https://git.sr.ht/~cadence/nodejs-pinski#20548ccee95dfdd9c5eea91476a88f402d4aac08", "version": "git+https://git.sr.ht/~cadence/nodejs-pinski#04cd72482574f689b78670b7f2b80209f5449a21",
"from": "git+https://git.sr.ht/~cadence/nodejs-pinski#20548ccee95dfdd9c5eea91476a88f402d4aac08", "from": "git+https://git.sr.ht/~cadence/nodejs-pinski#04cd72482574f689b78670b7f2b80209f5449a21",
"requires": { "requires": {
"mime": "^2.4.6", "mime": "^2.4.6",
"pug": "^3.0.0", "pug": "^3.0.0",
@ -240,6 +527,33 @@
"ws": "^7.3.1" "ws": "^7.3.1"
} }
}, },
"prebuild-install": {
"version": "5.3.5",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.5.tgz",
"integrity": "sha512-YmMO7dph9CYKi5IR/BzjOJlRzpxGGVo1EsLSUZ0mt/Mq0HWZIHOKHHcHdT69yG54C9m6i45GpItwRHpk0Py7Uw==",
"requires": {
"detect-libc": "^1.0.3",
"expand-template": "^2.0.3",
"github-from-package": "0.0.0",
"minimist": "^1.2.3",
"mkdirp": "^0.5.1",
"napi-build-utils": "^1.0.1",
"node-abi": "^2.7.0",
"noop-logger": "^0.1.1",
"npmlog": "^4.0.1",
"pump": "^3.0.0",
"rc": "^1.2.7",
"simple-get": "^3.0.3",
"tar-fs": "^2.0.0",
"tunnel-agent": "^0.6.0",
"which-pm-runs": "^1.0.0"
}
},
"process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
"promise": { "promise": {
"version": "7.3.1", "version": "7.3.1",
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
@ -360,6 +674,40 @@
"resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz",
"integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==" "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ=="
}, },
"pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
"requires": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"requires": {
"deep-extend": "^0.6.0",
"ini": "~1.3.0",
"minimist": "^1.2.0",
"strip-json-comments": "~2.0.1"
}
},
"readable-stream": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"readdirp": { "readdirp": {
"version": "3.4.0", "version": "3.4.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz",
@ -376,6 +724,11 @@
"path-parse": "^1.0.6" "path-parse": "^1.0.6"
} }
}, },
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"sass": { "sass": {
"version": "1.26.10", "version": "1.26.10",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.26.10.tgz", "resolved": "https://registry.npmjs.org/sass/-/sass-1.26.10.tgz",
@ -384,6 +737,116 @@
"chokidar": ">=2.0.0 <4.0.0" "chokidar": ">=2.0.0 <4.0.0"
} }
}, },
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
},
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
},
"signal-exit": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
},
"simple-concat": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="
},
"simple-get": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz",
"integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==",
"requires": {
"decompress-response": "^4.2.0",
"once": "^1.3.1",
"simple-concat": "^1.0.0"
}
},
"string-width": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
"strip-ansi": "^3.0.0"
}
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
}
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"requires": {
"ansi-regex": "^2.0.0"
}
},
"strip-json-comments": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
},
"tar": {
"version": "4.4.10",
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.10.tgz",
"integrity": "sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA==",
"requires": {
"chownr": "^1.1.1",
"fs-minipass": "^1.2.5",
"minipass": "^2.3.5",
"minizlib": "^1.2.1",
"mkdirp": "^0.5.0",
"safe-buffer": "^5.1.2",
"yallist": "^3.0.3"
}
},
"tar-fs": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.0.tgz",
"integrity": "sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg==",
"requires": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
"pump": "^3.0.0",
"tar-stream": "^2.0.0"
}
},
"tar-stream": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.3.tgz",
"integrity": "sha512-Z9yri56Dih8IaK8gncVPx4Wqt86NDmQTSh49XLZgjWpGZL9GK9HKParS2scqHCC4w6X9Gh2jwaU45V47XTKwVA==",
"requires": {
"bl": "^4.0.1",
"end-of-stream": "^1.4.1",
"fs-constants": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.1.1"
},
"dependencies": {
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
}
}
},
"to-fast-properties": { "to-fast-properties": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@ -402,11 +865,37 @@
"resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz",
"integrity": "sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ=" "integrity": "sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ="
}, },
"tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
"requires": {
"safe-buffer": "^5.0.1"
}
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"void-elements": { "void-elements": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
"integrity": "sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=" "integrity": "sha1-YU9/v42AHwu18GYfWy9XhXUOTwk="
}, },
"which-pm-runs": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz",
"integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs="
},
"wide-align": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
"integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
"requires": {
"string-width": "^1.0.2 || 2"
}
},
"with": { "with": {
"version": "7.0.2", "version": "7.0.2",
"resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz",
@ -418,10 +907,20 @@
"babel-walk": "3.0.0-canary-5" "babel-walk": "3.0.0-canary-5"
} }
}, },
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
"ws": { "ws": {
"version": "7.3.1", "version": "7.3.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz",
"integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==" "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA=="
},
"yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
} }
} }
} }

View File

@ -10,7 +10,9 @@
"author": "", "author": "",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
"better-sqlite3": "^7.1.0",
"cookie": "^0.4.1",
"node-fetch": "^2.6.0", "node-fetch": "^2.6.0",
"pinski": "git+https://git.sr.ht/~cadence/nodejs-pinski#20548ccee95dfdd9c5eea91476a88f402d4aac08" "pinski": "git+https://git.sr.ht/~cadence/nodejs-pinski#04cd72482574f689b78670b7f2b80209f5449a21"
} }
} }

View File

@ -1,6 +1,11 @@
extends includes/layout.pug extends includes/layout
include includes/video-list-item.pug include includes/video-list-item
include includes/subscribe-button
block head
title= `${data.author} - CloudTube`
script(type="module" src=getStaticURL("html", "/static/js/channel.js"))
block content block content
main.channel-page main.channel-page
@ -18,8 +23,7 @@ block content
.about .about
.name= data.author .name= data.author
.subscribers= data.second__subCountText || `${data.subCount} subscribers` .subscribers= data.second__subCountText || `${data.subCount} subscribers`
form(method="post" action=`/formapi/subscribe/${data.authorId}`).subscribe +subscribe_button(data.authorId, subscribed, `/channel/${data.authorId}`).subscribe-button.base-border-look
button.subscribe-button.base-border-look Subscribe
.description!= data.descriptionHtml .description!= data.descriptionHtml
.videos .videos

View File

@ -1,5 +1,8 @@
extends includes/layout.pug extends includes/layout.pug
block head
title Home - CloudTube
block content block content
main.home-page main.home-page
h1.top-header CloudTube h1.top-header CloudTube

View File

@ -0,0 +1,6 @@
mixin subscribe_button(ucid, isSubscribed, referrer)
- const subscribePath = !subscribed ? "subscribe" : "unsubscribe"
form(method="post" action=`/formapi/${subscribePath}/${ucid}`).subscribe-form
if referrer
input(type="hidden" name="referrer" value=referrer)
button(data-subscribed=(+isSubscribed) data-ucid=ucid)&attributes(attributes)#subscribe= !subscribed ? "Subscribe" : "Unsubscribe"

View File

@ -2,6 +2,9 @@ extends includes/layout.pug
include includes/video-list-item.pug include includes/video-list-item.pug
block head
title= `${query} (search) - CloudTube`
block content block content
main.search-page main.search-page
each result in results each result in results

View File

@ -1,9 +1,10 @@
extends includes/layout.pug extends includes/layout
include includes/video-list-item.pug include includes/video-list-item
include includes/subscribe-button
block head block head
title= video.title title= `${video.title} - CloudTube`
script(type="module" src=getStaticURL("html", "/static/js/player.js")) script(type="module" src=getStaticURL("html", "/static/js/player.js"))
script const data = !{JSON.stringify(video)} script const data = !{JSON.stringify(video)}
@ -36,21 +37,20 @@ block content
#audio-loading-display #audio-loading-display
.video-button-container .video-button-container
button.border-look#subscribe Subscribe +subscribe_button(video.authorId, subscribed, `/watch?v=${video.videoId}`).border-look
button.border-look#theatre Theatre //- button.border-look#theatre Theatre
select(autocomplete="off").border-look#quality-select select(autocomplete="off").border-look#quality-select
each f in sortedFormatStreams each f in sortedFormatStreams
option(value=f.itag)= `${f.qualityLabel} ${f.container}` option(value=f.itag)= `${f.qualityLabel} ${f.container}`
each f in sortedVideoAdaptiveFormats each f in sortedVideoAdaptiveFormats
option(value=f.itag)= `${f.qualityLabel} ${f.container} *` option(value=f.itag)= `${f.qualityLabel} ${f.container} *`
//-
.video-button-container
a(href="/subscriptions").border-look a(href="/subscriptions").border-look
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.border-look YouTube a(href=`https://www.youtube.com/watch?v=${video.videoId}`).border-look YouTube
a.border-look Iv: Snopyta a(href=`https://invidio.us/watch?v=${video.videoId}`).border-look Invidious
.description!= video.descriptionHtml .description!= video.descriptionHtml

View File

@ -43,7 +43,7 @@
color: c.$fg-main color: c.$fg-main
font-size: 18px font-size: 18px
.subscribe .subscribe-form
margin-top: 24px margin-top: 24px
.subscribe-button .subscribe-button

View File

@ -64,6 +64,9 @@
padding: 12px padding: 12px
border-radius: 4px border-radius: 4px
.subscribe-form
display: inline-block
.related-header .related-header
margin: 4px 0px 12px 2px margin: 4px 0px 12px 2px
font-weight: normal font-weight: normal

View File

@ -1,23 +1,27 @@
const {Pinski} = require("pinski") const {Pinski} = require("pinski")
const {setInstance} = require("pinski/plugins") const {setInstance} = require("pinski/plugins")
const server = new Pinski({ ;(async () => {
await require("./api/utils/upgradedb")()
const server = new Pinski({
port: 8080, port: 8080,
relativeRoot: __dirname, relativeRoot: __dirname,
filesDir: "html" filesDir: "html"
}) })
setInstance(server) setInstance(server)
server.addSassDir("sass", ["sass/includes"]) server.addSassDir("sass", ["sass/includes"])
server.addRoute("/static/css/main.css", "sass/main.sass", "sass") server.addRoute("/static/css/main.css", "sass/main.sass", "sass")
server.addPugDir("pug", ["pug/includes"]) server.addPugDir("pug", ["pug/includes"])
server.addRoute("/", "pug/home.pug", "pug") server.addRoute("/", "pug/home.pug", "pug")
server.addStaticHashTableDir("html/static/js") server.addStaticHashTableDir("html/static/js")
server.addStaticHashTableDir("html/static/js/elemjs") server.addStaticHashTableDir("html/static/js/elemjs")
server.addAPIDir("api") server.addAPIDir("api")
server.startServer() server.startServer()
})()