mirror of
				https://git.sr.ht/~cadence/cloudtube
				synced 2025-10-30 11:05:36 +00:00 
			
		
		
		
	Add database and subscribe button
This commit is contained in:
		
							parent
							
								
									2af05e43a9
								
							
						
					
					
						commit
						f24e1bb39c
					
				
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -6,3 +6,4 @@ | |||||||
| 
 | 
 | ||||||
| # Auto-generated files | # Auto-generated files | ||||||
| node_modules | node_modules | ||||||
|  | /db | ||||||
| @ -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
									
								
							
							
						
						
									
										56
									
								
								api/formapi.js
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										12
									
								
								api/utils/constants.js
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										8
									
								
								api/utils/db.js
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										78
									
								
								api/utils/getuser.js
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										55
									
								
								api/utils/upgradedb.js
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										83
									
								
								api/utils/validate.js
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										15
									
								
								api/utils/youtube.js
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										19
									
								
								api/video.js
									
									
									
									
									
										Normal 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}) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | ] | ||||||
| @ -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; | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								html/static/js/channel.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								html/static/js/channel.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | |||||||
|  | import {q} from "/static/js/elemjs/elemjs.js" | ||||||
|  | import {SubscribeButton} from "/static/js/subscribe.js" | ||||||
|  | 
 | ||||||
|  | new SubscribeButton(q("#subscribe")) | ||||||
| @ -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") | ||||||
| }) | }) | ||||||
|  | |||||||
| @ -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")) | ||||||
|  | |||||||
							
								
								
									
										28
									
								
								html/static/js/subscribe.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								html/static/js/subscribe.js
									
									
									
									
									
										Normal 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
									
									
									
								
							
							
						
						
									
										503
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -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==" | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -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" | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -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 | ||||||
|  | |||||||
| @ -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 | ||||||
|  | |||||||
							
								
								
									
										6
									
								
								pug/includes/subscribe-button.pug
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								pug/includes/subscribe-button.pug
									
									
									
									
									
										Normal 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" | ||||||
| @ -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 | ||||||
|  | |||||||
| @ -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 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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 | ||||||
|  | |||||||
| @ -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 | ||||||
|  | |||||||
							
								
								
									
										26
									
								
								server.js
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								server.js
									
									
									
									
									
								
							| @ -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() | ||||||
|  | })() | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user