diff --git a/.gitignore b/.gitignore index 32c7cda..564cdcf 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ .vscode # Auto-generated files -node_modules \ No newline at end of file +node_modules +/db \ No newline at end of file diff --git a/api/channels.js b/api/channels.js index cc352f3..3337635 100644 --- a/api/channels.js +++ b/api/channels.js @@ -1,12 +1,16 @@ -const fetch = require("node-fetch") const {render} = require("pinski/plugins") +const constants = require("./utils/constants") +const {fetchChannel} = require("./utils/youtube") +const {getUser} = require("./utils/getuser") 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 data = await fetch(`http://localhost:3000/api/v1/channels/${id}`).then(res => res.json()) - return render(200, "pug/channel.pug", {data}) + const data = await fetchChannel(id) + const user = getUser(req) + const subscribed = user.isSubscribed(id) + return render(200, "pug/channel.pug", {data, subscribed}) } } ] diff --git a/api/formapi.js b/api/formapi.js new file mode 100644 index 0000000..4c43792 --- /dev/null +++ b/api/formapi.js @@ -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() + } + } +] diff --git a/api/utils/constants.js b/api/utils/constants.js new file mode 100644 index 0000000..3a1dbbd --- /dev/null +++ b/api/utils/constants.js @@ -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 diff --git a/api/utils/db.js b/api/utils/db.js new file mode 100644 index 0000000..1ceab92 --- /dev/null +++ b/api/utils/db.js @@ -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 diff --git a/api/utils/getuser.js b/api/utils/getuser.js new file mode 100644 index 0000000..12e7141 --- /dev/null +++ b/api/utils/getuser.js @@ -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 diff --git a/api/utils/upgradedb.js b/api/utils/upgradedb.js new file mode 100644 index 0000000..15b4bd5 --- /dev/null +++ b/api/utils/upgradedb.js @@ -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) + } + } +} diff --git a/api/utils/validate.js b/api/utils/validate.js new file mode 100644 index 0000000..3cf9962 --- /dev/null +++ b/api/utils/validate.js @@ -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 diff --git a/api/utils/youtube.js b/api/utils/youtube.js new file mode 100644 index 0000000..85832d6 --- /dev/null +++ b/api/utils/youtube.js @@ -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 diff --git a/api/video.js b/api/video.js new file mode 100644 index 0000000..cafdf6d --- /dev/null +++ b/api/video.js @@ -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}) + } + } +] diff --git a/api/youtube.js b/api/youtube.js index 2dafdc2..33bd76f 100644 --- a/api/youtube.js +++ b/api/youtube.js @@ -177,14 +177,14 @@ function fetchChannel(channelID, ignoreCache) { } module.exports = [ - { + /*{ route: "/watch", methods: ["GET"], code: async ({url}) => { const id = url.searchParams.get("v") 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}) } - } - /* + }, + { route: "/v/(.*)", methods: ["GET"], code: async ({fill}) => { let id; diff --git a/html/static/js/channel.js b/html/static/js/channel.js new file mode 100644 index 0000000..3e202ce --- /dev/null +++ b/html/static/js/channel.js @@ -0,0 +1,4 @@ +import {q} from "/static/js/elemjs/elemjs.js" +import {SubscribeButton} from "/static/js/subscribe.js" + +new SubscribeButton(q("#subscribe")) diff --git a/html/static/js/focus.js b/html/static/js/focus.js index 12b2dc3..2413484 100644 --- a/html/static/js/focus.js +++ b/html/static/js/focus.js @@ -1,3 +1,5 @@ +document.body.classList.remove("show-focus") + document.addEventListener("mousedown", () => { document.body.classList.remove("show-focus") }) diff --git a/html/static/js/player.js b/html/static/js/player.js index ed4b282..3421750 100644 --- a/html/static/js/player.js +++ b/html/static/js/player.js @@ -1,4 +1,5 @@ import {q, ElemJS} from "/static/js/elemjs/elemjs.js" +import {SubscribeButton} from "/static/js/subscribe.js" const video = q("#video") const audio = q("#audio") @@ -67,6 +68,7 @@ class QualitySelect extends ElemJS { onInput() { const itag = this.element.value formatLoader.play(itag) + video.focus() } } @@ -118,3 +120,40 @@ for (let eventName of ["canplaythrough", "waiting", "stalled"]) { video.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")) diff --git a/html/static/js/subscribe.js b/html/static/js/subscribe.js new file mode 100644 index 0000000..d6c065b --- /dev/null +++ b/html/static/js/subscribe.js @@ -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 +} diff --git a/package-lock.json b/package-lock.json index 57ba583..26e3ada 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,11 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", "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": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", @@ -38,6 +43,20 @@ "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": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -56,11 +75,56 @@ "@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": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", "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": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", @@ -69,6 +133,15 @@ "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": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", @@ -92,6 +165,21 @@ "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": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", @@ -101,11 +189,62 @@ "@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": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", "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": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -114,12 +253,45 @@ "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": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", "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": { "version": "5.1.1", "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", "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": { "version": "2.1.0", "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", "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": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", @@ -181,6 +381,11 @@ "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": { "version": "1.0.2", "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", "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": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", "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": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "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": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "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": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", @@ -231,8 +518,8 @@ "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" }, "pinski": { - "version": "git+https://git.sr.ht/~cadence/nodejs-pinski#20548ccee95dfdd9c5eea91476a88f402d4aac08", - "from": "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#04cd72482574f689b78670b7f2b80209f5449a21", "requires": { "mime": "^2.4.6", "pug": "^3.0.0", @@ -240,6 +527,33 @@ "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": { "version": "7.3.1", "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", "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": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", @@ -376,6 +724,11 @@ "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": { "version": "1.26.10", "resolved": "https://registry.npmjs.org/sass/-/sass-1.26.10.tgz", @@ -384,6 +737,116 @@ "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": { "version": "2.0.0", "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", "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": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", "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": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", @@ -418,10 +907,20 @@ "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": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==" + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" } } } diff --git a/package.json b/package.json index cc539bd..db49bb4 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,9 @@ "author": "", "license": "AGPL-3.0", "dependencies": { + "better-sqlite3": "^7.1.0", + "cookie": "^0.4.1", "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" } } diff --git a/pug/channel.pug b/pug/channel.pug index fddaf7f..f58ff0d 100644 --- a/pug/channel.pug +++ b/pug/channel.pug @@ -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 main.channel-page @@ -18,8 +23,7 @@ block content .about .name= data.author .subscribers= data.second__subCountText || `${data.subCount} subscribers` - form(method="post" action=`/formapi/subscribe/${data.authorId}`).subscribe - button.subscribe-button.base-border-look Subscribe + +subscribe_button(data.authorId, subscribed, `/channel/${data.authorId}`).subscribe-button.base-border-look .description!= data.descriptionHtml .videos diff --git a/pug/home.pug b/pug/home.pug index 575d011..784c00b 100644 --- a/pug/home.pug +++ b/pug/home.pug @@ -1,5 +1,8 @@ extends includes/layout.pug +block head + title Home - CloudTube + block content main.home-page h1.top-header CloudTube diff --git a/pug/includes/subscribe-button.pug b/pug/includes/subscribe-button.pug new file mode 100644 index 0000000..5d9878a --- /dev/null +++ b/pug/includes/subscribe-button.pug @@ -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" diff --git a/pug/search.pug b/pug/search.pug index 2c58d31..7e2052a 100644 --- a/pug/search.pug +++ b/pug/search.pug @@ -2,8 +2,11 @@ extends includes/layout.pug include includes/video-list-item.pug +block head + title= `${query} (search) - CloudTube` + block content main.search-page each result in results .search-result - +video_list_item(result) \ No newline at end of file + +video_list_item(result) diff --git a/pug/video.pug b/pug/video.pug index 64c42a0..938e885 100644 --- a/pug/video.pug +++ b/pug/video.pug @@ -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 - title= video.title + title= `${video.title} - CloudTube` script(type="module" src=getStaticURL("html", "/static/js/player.js")) script const data = !{JSON.stringify(video)} @@ -36,21 +37,20 @@ block content #audio-loading-display .video-button-container - button.border-look#subscribe Subscribe - button.border-look#theatre Theatre + +subscribe_button(video.authorId, subscribed, `/watch?v=${video.videoId}`).border-look + //- button.border-look#theatre Theatre select(autocomplete="off").border-look#quality-select each f in sortedFormatStreams option(value=f.itag)= `${f.qualityLabel} ${f.container}` each f in sortedVideoAdaptiveFormats option(value=f.itag)= `${f.qualityLabel} ${f.container} *` - - .video-button-container - a(href="/subscriptions").border-look - img(src="/static/images/search.svg" width=17 height=17 alt="").button-icon - | Search - button.border-look#share Share - a.border-look YouTube - a.border-look Iv: Snopyta + //- + a(href="/subscriptions").border-look + img(src="/static/images/search.svg" width=17 height=17 alt="").button-icon + | Search + //- button.border-look#share Share + a(href=`https://www.youtube.com/watch?v=${video.videoId}`).border-look YouTube + a(href=`https://invidio.us/watch?v=${video.videoId}`).border-look Invidious .description!= video.descriptionHtml diff --git a/sass/includes/channel-page.sass b/sass/includes/channel-page.sass index dcb8fbd..f7aaf09 100644 --- a/sass/includes/channel-page.sass +++ b/sass/includes/channel-page.sass @@ -43,7 +43,7 @@ color: c.$fg-main font-size: 18px - .subscribe + .subscribe-form margin-top: 24px .subscribe-button diff --git a/sass/includes/video-page.sass b/sass/includes/video-page.sass index b8447cb..51875d6 100644 --- a/sass/includes/video-page.sass +++ b/sass/includes/video-page.sass @@ -64,6 +64,9 @@ padding: 12px border-radius: 4px +.subscribe-form + display: inline-block + .related-header margin: 4px 0px 12px 2px font-weight: normal diff --git a/server.js b/server.js index 39e468e..7e1f60c 100644 --- a/server.js +++ b/server.js @@ -1,23 +1,27 @@ const {Pinski} = require("pinski") const {setInstance} = require("pinski/plugins") -const server = new Pinski({ - port: 8080, - relativeRoot: __dirname, - filesDir: "html" -}) +;(async () => { + await require("./api/utils/upgradedb")() -setInstance(server) + const server = new Pinski({ + port: 8080, + relativeRoot: __dirname, + filesDir: "html" + }) -server.addSassDir("sass", ["sass/includes"]) -server.addRoute("/static/css/main.css", "sass/main.sass", "sass") + setInstance(server) -server.addPugDir("pug", ["pug/includes"]) -server.addRoute("/", "pug/home.pug", "pug") + server.addSassDir("sass", ["sass/includes"]) + server.addRoute("/static/css/main.css", "sass/main.sass", "sass") -server.addStaticHashTableDir("html/static/js") -server.addStaticHashTableDir("html/static/js/elemjs") + server.addPugDir("pug", ["pug/includes"]) + server.addRoute("/", "pug/home.pug", "pug") -server.addAPIDir("api") + server.addStaticHashTableDir("html/static/js") + server.addStaticHashTableDir("html/static/js/elemjs") -server.startServer() + server.addAPIDir("api") + + server.startServer() +})()