Either google apis is the most scuffed thing ever, or it's too genius for my puny mind

This commit is contained in:
Eclypsed
2024-02-11 01:03:49 -05:00
parent 09a23fe363
commit cb03d2661b
12 changed files with 548 additions and 33 deletions

435
package-lock.json generated
View File

@@ -13,7 +13,10 @@
"@types/jsonwebtoken": "^9.0.5",
"bcrypt-ts": "^5.0.1",
"better-sqlite3": "^9.3.0",
"googleapis": "^133.0.0",
"jsonwebtoken": "^9.0.2",
"pocketbase": "^0.21.1",
"ytdl-core": "^4.11.5",
"zod": "^3.22.4"
},
"devDependencies": {
@@ -857,6 +860,17 @@
"node": ">=0.4.0"
}
},
"node_modules/agent-base": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz",
"integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==",
"dependencies": {
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/ansi-regex": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
@@ -1004,6 +1018,14 @@
"prebuild-install": "^7.1.1"
}
},
"node_modules/bignumber.js": {
"version": "9.1.2",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz",
"integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==",
"engines": {
"node": "*"
}
},
"node_modules/binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
@@ -1122,6 +1144,23 @@
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
},
"node_modules/call-bind": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.6.tgz",
"integrity": "sha512-Mj50FLHtlsoVfRfnHaZvyrooHcrlceNZdL/QBvJJVd9Ta55qCQK0gs4ss2oZDeV9zFCs6ewzYgVE5yfVmfFpVg==",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.3",
"set-function-length": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -1290,7 +1329,6 @@
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"dependencies": {
"ms": "2.1.2"
},
@@ -1334,6 +1372,20 @@
"node": ">=0.10.0"
}
},
"node_modules/define-data-property": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.2.tgz",
"integrity": "sha512-SRtsSqsDbgpJBbW3pABMCOt6rQyeM8s8RiyeSN8jYG8sYmt/kGJejbydttUsnDs1tadr19tvhT4ShwMyoqAm4g==",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.2",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
@@ -1412,6 +1464,14 @@
"once": "^1.4.0"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es6-promise": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
@@ -1488,6 +1548,11 @@
"node": ">=6"
}
},
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"node_modules/fast-glob": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
@@ -1588,7 +1653,50 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/gaxios": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.2.0.tgz",
"integrity": "sha512-H6+bHeoEAU5D6XNc6mPKeN5dLZqEDs9Gpk6I+SZBEzK5So58JVrHPmevNi35fRl1J9Y5TaeLW0kYx3pCJ1U2mQ==",
"dependencies": {
"extend": "^3.0.2",
"https-proxy-agent": "^7.0.1",
"is-stream": "^2.0.0",
"node-fetch": "^2.6.9"
},
"engines": {
"node": ">=14"
}
},
"node_modules/gcp-metadata": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz",
"integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==",
"dependencies": {
"gaxios": "^6.0.0",
"json-bigint": "^1.0.0"
},
"engines": {
"node": ">=14"
}
},
"node_modules/get-intrinsic": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"has-proto": "^1.0.1",
"has-symbols": "^1.0.3",
"hasown": "^2.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -1642,17 +1750,154 @@
"integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==",
"dev": true
},
"node_modules/google-auth-library": {
"version": "9.6.3",
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.6.3.tgz",
"integrity": "sha512-4CacM29MLC2eT9Cey5GDVK4Q8t+MMp8+OEdOaqD9MG6b0dOyLORaaeJMPQ7EESVgm/+z5EKYyFLxgzBJlJgyHQ==",
"dependencies": {
"base64-js": "^1.3.0",
"ecdsa-sig-formatter": "^1.0.11",
"gaxios": "^6.1.1",
"gcp-metadata": "^6.1.0",
"gtoken": "^7.0.0",
"jws": "^4.0.0"
},
"engines": {
"node": ">=14"
}
},
"node_modules/google-auth-library/node_modules/jwa": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz",
"integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==",
"dependencies": {
"buffer-equal-constant-time": "1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/google-auth-library/node_modules/jws": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
"integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
"dependencies": {
"jwa": "^2.0.0",
"safe-buffer": "^5.0.1"
}
},
"node_modules/googleapis": {
"version": "133.0.0",
"resolved": "https://registry.npmjs.org/googleapis/-/googleapis-133.0.0.tgz",
"integrity": "sha512-6xyc49j+x7N4smawJs/q1i7mbSkt6SYUWWd9RbsmmDW7gRv+mhwZ4xT+XkPihZcNyo/diF//543WZq4szdS74w==",
"dependencies": {
"google-auth-library": "^9.0.0",
"googleapis-common": "^7.0.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/googleapis-common": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-7.0.1.tgz",
"integrity": "sha512-mgt5zsd7zj5t5QXvDanjWguMdHAcJmmDrF9RkInCecNsyV7S7YtGqm5v2IWONNID88osb7zmx5FtrAP12JfD0w==",
"dependencies": {
"extend": "^3.0.2",
"gaxios": "^6.0.3",
"google-auth-library": "^9.0.0",
"qs": "^6.7.0",
"url-template": "^2.0.8",
"uuid": "^9.0.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/gopd": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
"dependencies": {
"get-intrinsic": "^1.1.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true
},
"node_modules/gtoken": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz",
"integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==",
"dependencies": {
"gaxios": "^6.0.0",
"jws": "^4.0.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/gtoken/node_modules/jwa": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz",
"integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==",
"dependencies": {
"buffer-equal-constant-time": "1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/gtoken/node_modules/jws": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
"integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
"dependencies": {
"jwa": "^2.0.0",
"safe-buffer": "^5.0.1"
}
},
"node_modules/has-property-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz",
"integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==",
"dependencies": {
"get-intrinsic": "^1.2.2"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
"integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.2"
},
@@ -1660,6 +1905,18 @@
"node": ">= 0.4"
}
},
"node_modules/https-proxy-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz",
"integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==",
"dependencies": {
"agent-base": "^7.0.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@@ -1797,6 +2054,17 @@
"@types/estree": "*"
}
},
"node_modules/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -1830,6 +2098,14 @@
"jiti": "bin/jiti.js"
}
},
"node_modules/json-bigint": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
"integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
"dependencies": {
"bignumber.js": "^9.0.0"
}
},
"node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
@@ -1944,6 +2220,18 @@
"node": "14 || >=16.14"
}
},
"node_modules/m3u8stream": {
"version": "0.8.6",
"resolved": "https://registry.npmjs.org/m3u8stream/-/m3u8stream-0.8.6.tgz",
"integrity": "sha512-LZj8kIVf9KCphiHmH7sbFQTVe4tOemb202fWwvJwR9W5ENW/1hxJN6ksAWGhQgSBSa3jyWhnjKU1Fw1GaOdbyA==",
"dependencies": {
"miniget": "^4.2.2",
"sax": "^1.2.4"
},
"engines": {
"node": ">=12"
}
},
"node_modules/magic-string": {
"version": "0.30.5",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz",
@@ -2004,6 +2292,14 @@
"node": ">=4"
}
},
"node_modules/miniget": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/miniget/-/miniget-4.2.3.tgz",
"integrity": "sha512-SjbDPDICJ1zT+ZvQwK0hUcRY4wxlhhNpHL9nJOB2MEAXRGagTljsO8MEDzQMTFf0Q8g4QNi8P9lEm/g7e+qgzA==",
"engines": {
"node": ">=12"
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -2118,6 +2414,25 @@
"node": ">=10"
}
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/node-releases": {
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
@@ -2160,6 +2475,14 @@
"node": ">= 6"
}
},
"node_modules/object-inspect": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -2267,6 +2590,11 @@
"node": ">= 6"
}
},
"node_modules/pocketbase": {
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.21.1.tgz",
"integrity": "sha512-0PvCP4pKtxsV9kwldEGyibEvhwOcx9jSCrz3WN5CgPILJfM0z76f1op9WE8/8UgikDsMdRsc5iBLfKintrJS1g=="
},
"node_modules/postcss": {
"version": "8.4.33",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz",
@@ -2541,6 +2869,20 @@
"once": "^1.3.1"
}
},
"node_modules/qs": {
"version": "6.11.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz",
"integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==",
"dependencies": {
"side-channel": "^1.0.4"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -2755,6 +3097,11 @@
"rimraf": "^2.5.2"
}
},
"node_modules/sax": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz",
"integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA=="
},
"node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
@@ -2786,6 +3133,22 @@
"integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==",
"dev": true
},
"node_modules/set-function-length": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz",
"integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==",
"dependencies": {
"define-data-property": "^1.1.2",
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.3",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -2807,6 +3170,23 @@
"node": ">=8"
}
},
"node_modules/side-channel": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz",
"integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==",
"dependencies": {
"call-bind": "^1.0.6",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.4",
"object-inspect": "^1.13.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
@@ -3353,6 +3733,11 @@
"node": ">=6"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/ts-interface-checker": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
@@ -3424,11 +3809,28 @@
"browserslist": ">= 4.21.0"
}
},
"node_modules/url-template": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz",
"integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw=="
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/vite": {
"version": "5.0.12",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz",
@@ -3498,6 +3900,20 @@
}
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -3623,6 +4039,19 @@
"node": ">= 14"
}
},
"node_modules/ytdl-core": {
"version": "4.11.5",
"resolved": "https://registry.npmjs.org/ytdl-core/-/ytdl-core-4.11.5.tgz",
"integrity": "sha512-27LwsW4n4nyNviRCO1hmr8Wr5J1wLLMawHCQvH8Fk0hiRqrxuIu028WzbJetiYH28K8XDbeinYW4/wcHQD1EXA==",
"dependencies": {
"m3u8stream": "^0.8.6",
"miniget": "^4.2.2",
"sax": "^1.1.3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/zod": {
"version": "3.22.4",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz",

View File

@@ -32,7 +32,10 @@
"@types/jsonwebtoken": "^9.0.5",
"bcrypt-ts": "^5.0.1",
"better-sqlite3": "^9.3.0",
"googleapis": "^133.0.0",
"jsonwebtoken": "^9.0.2",
"pocketbase": "^0.21.1",
"ytdl-core": "^4.11.5",
"zod": "^3.22.4"
}
}

21
src/app.d.ts vendored
View File

@@ -27,11 +27,17 @@ declare global {
urlOrigin: string
}
interface Tokens {
accessToken: string
refreshToken?: string
expiry?: number
}
interface Connection {
id: string
user: User
service: Service
accessToken: string
tokens: Tokens
}
// These Schemas should only contain general info data that is necessary for data fetching purposes.
@@ -93,8 +99,13 @@ declare global {
serverName: string
}
interface JFTokens implements Tokens {
accessToken: string
}
interface JFConnection extends Connection {
service: JFService
tokens: JFTokens
}
interface AuthData {
@@ -168,10 +179,18 @@ declare global {
interface YTService extends Service {
type: 'youtube-music'
username: string
profilePicture?: string
}
interface YTTokens implements Tokens {
accessToken: string,
refreshToken: string,
expiry: number
}
interface YTConnection extends Connection {
service: YTService
tokens: YTTokens
}
}
}

View File

@@ -1,12 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
<script src="https://accounts.google.com/gsi/client" async defer></script>
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

Binary file not shown.

View File

@@ -5,7 +5,7 @@ import { isValidURL } from '$lib/utils'
const db = new Database('./src/lib/server/users.db', { verbose: console.info })
db.pragma('foreign_keys = ON')
const initUsersTable = 'CREATE TABLE IF NOT EXISTS Users(id VARCHAR(36) PRIMARY KEY, username VARCHAR(30) UNIQUE NOT NULL, password VARCHAR(72) NOT NULL)'
const initConnectionsTable = 'CREATE TABLE IF NOT EXISTS Connections(id VARCHAR(36) PRIMARY KEY, userId VARCHAR(36) NOT NULL, service TEXT NOT NULL, accessToken TEXT NOT NULL, FOREIGN KEY(userId) REFERENCES Users(id))'
const initConnectionsTable = 'CREATE TABLE IF NOT EXISTS Connections(id VARCHAR(36) PRIMARY KEY, userId VARCHAR(36) NOT NULL, service TEXT NOT NULL, tokens TEXT NOT NULL, FOREIGN KEY(userId) REFERENCES Users(id))'
db.exec(initUsersTable), db.exec(initConnectionsTable)
type UserQueryParams = {
@@ -16,7 +16,7 @@ interface ConnectionsTableSchema {
id: string
userId: string
service: string
accessToken: string
tokens: string
}
export class Users {
@@ -52,8 +52,8 @@ export class Users {
export class Connections {
static getConnection = (id: string): Connection => {
const { userId, service, accessToken } = db.prepare('SELECT * FROM Connections WHERE id = ?').get(id) as ConnectionsTableSchema
const connection: Connection = { id, user: Users.getUser(userId)!, service: JSON.parse(service), accessToken }
const { userId, service, tokens } = db.prepare('SELECT * FROM Connections WHERE id = ?').get(id) as ConnectionsTableSchema
const connection: Connection = { id, user: Users.getUser(userId)!, service: JSON.parse(service), tokens: JSON.parse(tokens) }
return connection
}
@@ -62,16 +62,16 @@ export class Connections {
const connections: Connection[] = []
const user = Users.getUser(userId)!
connectionRows.forEach((row) => {
const { id, service, accessToken } = row
connections.push({ id, user, service: JSON.parse(service), accessToken })
const { id, service, tokens } = row
connections.push({ id, user, service: JSON.parse(service), tokens: JSON.parse(tokens) })
})
return connections
}
static addConnection = (userId: string, service: Service, accessToken: string): Connection => {
static addConnection = (userId: string, service: Service, tokens: Tokens): Connection => {
const connectionId = generateUUID()
if (!isValidURL(service.urlOrigin)) throw new Error('Service does not have valid url')
db.prepare('INSERT INTO Connections(id, userId, service, accessToken) VALUES(?, ?, ?, ?)').run(connectionId, userId, JSON.stringify(service), accessToken)
db.prepare('INSERT INTO Connections(id, userId, service, tokens) VALUES(?, ?, ?, ?)').run(connectionId, userId, JSON.stringify(service), JSON.stringify(tokens))
return this.getConnection(connectionId)
}

View File

@@ -0,0 +1,4 @@
import type { PageServerLoad } from '../$types'
import ytdl from 'ytdl-core'
export const load: PageServerLoad = async ({ fetch }) => {}

View File

@@ -1 +1 @@
<h1>Welcome to the Search Page!</h1>
<h1>Search Page</h1>

View File

@@ -17,7 +17,11 @@ const newConnectionSchema = z.object({
userId: z.string(),
urlOrigin: z.string().refine((val) => isValidURL(val)),
}),
accessToken: z.string(),
tokens: z.object({
accessToken: z.string(),
refreshToken: z.string().optional(),
expiry: z.number().optional(),
}),
})
export const POST: RequestHandler = async ({ params, request }) => {
@@ -28,8 +32,8 @@ export const POST: RequestHandler = async ({ params, request }) => {
const connectionValidation = newConnectionSchema.safeParse(connection)
if (!connectionValidation.success) return new Response(connectionValidation.error.message, { status: 400 })
const { service, accessToken } = connection
const newConnection = Connections.addConnection(userId, service, accessToken)
const { service, tokens } = connection
const newConnection = Connections.addConnection(userId, service, tokens)
return Response.json(newConnection)
}

View File

@@ -13,7 +13,7 @@ export const GET: RequestHandler = async ({ params, fetch }) => {
const recommendations: Song[] = []
for (const connection of userConnections) {
const { service, accessToken } = connection
const { service, tokens } = connection
switch (service.type) {
case 'jellyfin':
@@ -26,7 +26,7 @@ export const GET: RequestHandler = async ({ params, fetch }) => {
})
const mostPlayedSongsURL = new URL(`/Users/${service.userId}/Items?${mostPlayedSongsSearchParams.toString()}`, service.urlOrigin).href
const requestHeaders = new Headers({ Authorization: `MediaBrowser Token="${accessToken}"` })
const requestHeaders = new Headers({ Authorization: `MediaBrowser Token="${tokens.accessToken}"` })
const mostPlayedResponse = await fetch(mostPlayedSongsURL, { headers: requestHeaders })
const mostPlayedData = await mostPlayedResponse.json()

View File

@@ -1,6 +1,8 @@
import { fail } from '@sveltejs/kit'
import { SECRET_INTERNAL_API_KEY } from '$env/static/private'
import { SECRET_INTERNAL_API_KEY, YOUTUBE_API_CLIENT_SECRET } from '$env/static/private'
import { PUBLIC_YOUTUBE_API_CLIENT_ID } from '$env/static/public'
import type { PageServerLoad, Actions } from './$types'
import { google } from 'googleapis'
export const load: PageServerLoad = async ({ fetch, locals }) => {
const connectionsResponse = await fetch(`/api/users/${locals.user.id}/connections`, {
@@ -53,10 +55,13 @@ export const actions: Actions = {
username: userData.Name,
serverName: systemData.ServerName,
}
const tokenData: Jellyfin.JFTokens = {
accessToken: authData.AccessToken,
}
const newConnectionResponse = await fetch(`/api/users/${locals.user.id}/connections`, {
method: 'POST',
headers: { apikey: SECRET_INTERNAL_API_KEY },
body: JSON.stringify({ service: serviceData, accessToken: authData.AccessToken }),
body: JSON.stringify({ service: serviceData, tokens: tokenData }),
})
if (!newConnectionResponse.ok) return fail(500, { message: 'Internal Server Error' })
@@ -64,6 +69,30 @@ export const actions: Actions = {
const newConnection: Jellyfin.JFConnection = await newConnectionResponse.json()
return { newConnection }
},
youtubeMusicLogin: async ({ request }) => {
const formData = await request.formData()
const { code } = Object.fromEntries(formData)
const client = new google.auth.OAuth2({ clientId: PUBLIC_YOUTUBE_API_CLIENT_ID, clientSecret: YOUTUBE_API_CLIENT_SECRET, redirectUri: 'http://localhost:5173' }) // DO NOT SHIP THIS. THE CLIENT SECRET SHOULD NOT BE MADE AVAILABLE TO USERS. MAKE A REQUEST TO THE LAZULI WEBSITE INSTEAD.
const { tokens } = await client.getToken(code.toString())
const tokenData: YouTubeMusic.YTTokens = {
accessToken: tokens.access_token as string,
refreshToken: tokens.refresh_token as string,
expiry: tokens.expiry_date as number,
}
const youtube = google.youtube('v3')
const userChannelResponse = await youtube.channels.list({ mine: true, part: ['id', 'snippet'], access_token: tokenData.accessToken })
const userChannel = userChannelResponse.data.items![0]
const serviceData: YouTubeMusic.YTService = {
type: 'youtube-music',
userId: userChannel.id as string,
urlOrigin: 'https://www.googleapis.com/youtube/v3',
username: userChannel.snippet?.title as string,
profilePicture: userChannel.snippet?.thumbnails?.default?.url as string | undefined,
}
},
deleteConnection: async ({ request, fetch, locals }) => {
const formData = await request.formData()
const connectionId = formData.get('connectionId')

View File

@@ -7,11 +7,13 @@
import { getDeviceUUID } from '$lib/utils'
import { SvelteComponent, type ComponentType } from 'svelte'
import ConnectionProfile from './connectionProfile.svelte'
import { enhance } from '$app/forms'
import { PUBLIC_YOUTUBE_API_CLIENT_ID } from '$env/static/public'
export let data: PageServerData
let connections = data.userConnections
const submitCredentials: SubmitFunction = ({ formData, action, cancel }) => {
const submitCredentials: SubmitFunction = async ({ formData, action, cancel }) => {
switch (action.search) {
case '?/authenticateJellyfin':
const { serverUrl, username, password } = Object.fromEntries(formData)
@@ -30,9 +32,14 @@
const deviceId = getDeviceUUID()
formData.append('deviceId', deviceId)
break
case '?/youtubeMusicLogin':
const code = await youtubeAuthenication()
formData.append('code', code)
break
case '?/deleteConnection':
break
default:
console.log(action.search)
cancel()
}
@@ -56,11 +63,28 @@
return ($newestAlert = ['success', `Deleted ${Services[serviceType].displayName}`])
}
} else if (result.type === 'redirect') {
window.open(result.location, '_blank')
}
}
}
let newConnectionModal: ComponentType<SvelteComponent<{ submitFunction: SubmitFunction }>> | null = null
const youtubeAuthenication = async (): Promise<string> => {
return new Promise((resolve) => {
// @ts-ignore (google variable is a global variable imported by html script tag)
const client = google.accounts.oauth2.initCodeClient({
client_id: PUBLIC_YOUTUBE_API_CLIENT_ID,
scope: 'https://www.googleapis.com/auth/youtube',
ux_mode: 'popup',
callback: (response: any) => {
resolve(response.code)
},
})
client.requestCode()
})
}
</script>
<main>
@@ -70,9 +94,11 @@
<button class="add-connection-button h-14 rounded-md" on:click={() => (newConnectionModal = JellyfinAuthBox)}>
<img src={Services.jellyfin.icon} alt="{Services.jellyfin.displayName} icon" class="aspect-square h-full p-2" />
</button>
<button class="add-connection-button h-14 rounded-md">
<img src={Services['youtube-music'].icon} alt="{Services['youtube-music'].displayName} icon" class="aspect-square h-full p-2" />
</button>
<form method="post" action="?/youtubeMusicLogin" use:enhance={submitCredentials}>
<button class="add-connection-button h-14 rounded-md">
<img src={Services['youtube-music'].icon} alt="{Services['youtube-music'].displayName} icon" class="aspect-square h-full p-2" />
</button>
</form>
</div>
</section>
<div class="grid gap-8">