From cb03d2661b1deb7fa18555488dbb28b56136db6c Mon Sep 17 00:00:00 2001 From: Eclypsed Date: Sun, 11 Feb 2024 01:03:49 -0500 Subject: [PATCH] Either google apis is the most scuffed thing ever, or it's too genius for my puny mind --- package-lock.json | 435 +++++++++++++++++- package.json | 3 + src/app.d.ts | 21 +- src/app.html | 19 +- src/lib/server/users.db | Bin 24576 -> 24576 bytes src/lib/server/users.ts | 16 +- src/routes/(app)/search/+page.server.ts | 4 + src/routes/(app)/search/+page.svelte | 2 +- .../api/users/[userId]/connections/+server.ts | 10 +- .../users/[userId]/recommendations/+server.ts | 4 +- .../settings/connections/+page.server.ts | 33 +- src/routes/settings/connections/+page.svelte | 34 +- 12 files changed, 548 insertions(+), 33 deletions(-) create mode 100644 src/routes/(app)/search/+page.server.ts diff --git a/package-lock.json b/package-lock.json index 13ba8a0..a9fb61f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 49bd70b..f2d0972 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/src/app.d.ts b/src/app.d.ts index 5c74c8f..7bcd9d1 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -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 } } } diff --git a/src/app.html b/src/app.html index 77a5ff5..ac989ce 100644 --- a/src/app.html +++ b/src/app.html @@ -1,12 +1,13 @@ - - - - - %sveltekit.head% - - -
%sveltekit.body%
- + + + + + %sveltekit.head% + + + +
%sveltekit.body%
+ diff --git a/src/lib/server/users.db b/src/lib/server/users.db index cda8afc4bfb4eef8d212ac7117bf274808d640ee..ef10ac1b33023fe6c4245a7e67d3ef15302b4bad 100644 GIT binary patch delta 391 zcmZoTz}RqraY7c400X}$|5^S;d;)y;HVX>O;^k`eW?>gsR%UF9oh-`N%2tw}otjs? zc`4s#fed3~Qv=JCGy~lfvlLTZ6HB91-NYmV3tba)BLmYUGcyZg^JKrwIemSkk0R10=LF>U|=f6j0Q delta 378 zcmZoTz}RqraY7akHv_*Y|5^S;eB6BZHVX<&<>hJ&U||MOJiM2i!}2j6Em~K6a!>K!UDgb)Z(n-O6P)NU$3Ins0fcLQwvWQe{;wDLJ#MRTz@Y! zBhRQjf5V8N#E9HY3U~Nbh_P&w0h<8JcLu)iK=;4p3v6OzWD5;#vh_`|NJ<2{$wW8V zz}#5Z#5~1VH_6f{QP(id*vKq7$;{N!IE5JVC!6`tP%un3G%z(cNj5i1OffOEG*3!4 WOHMXRO)^YMF-tZ|Ndvo<7#jic2X5s6 diff --git a/src/lib/server/users.ts b/src/lib/server/users.ts index 6b37049..a78503f 100644 --- a/src/lib/server/users.ts +++ b/src/lib/server/users.ts @@ -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) } diff --git a/src/routes/(app)/search/+page.server.ts b/src/routes/(app)/search/+page.server.ts new file mode 100644 index 0000000..5f26530 --- /dev/null +++ b/src/routes/(app)/search/+page.server.ts @@ -0,0 +1,4 @@ +import type { PageServerLoad } from '../$types' +import ytdl from 'ytdl-core' + +export const load: PageServerLoad = async ({ fetch }) => {} diff --git a/src/routes/(app)/search/+page.svelte b/src/routes/(app)/search/+page.svelte index 0d259ea..a16595c 100644 --- a/src/routes/(app)/search/+page.svelte +++ b/src/routes/(app)/search/+page.svelte @@ -1 +1 @@ -

Welcome to the Search Page!

+

Search Page

diff --git a/src/routes/api/users/[userId]/connections/+server.ts b/src/routes/api/users/[userId]/connections/+server.ts index d6b9ac8..1da6eb7 100644 --- a/src/routes/api/users/[userId]/connections/+server.ts +++ b/src/routes/api/users/[userId]/connections/+server.ts @@ -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) } diff --git a/src/routes/api/users/[userId]/recommendations/+server.ts b/src/routes/api/users/[userId]/recommendations/+server.ts index c95cf3e..323ddd4 100644 --- a/src/routes/api/users/[userId]/recommendations/+server.ts +++ b/src/routes/api/users/[userId]/recommendations/+server.ts @@ -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() diff --git a/src/routes/settings/connections/+page.server.ts b/src/routes/settings/connections/+page.server.ts index 7835b71..7144db1 100644 --- a/src/routes/settings/connections/+page.server.ts +++ b/src/routes/settings/connections/+page.server.ts @@ -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') diff --git a/src/routes/settings/connections/+page.svelte b/src/routes/settings/connections/+page.svelte index a30dd7f..31b0837 100644 --- a/src/routes/settings/connections/+page.svelte +++ b/src/routes/settings/connections/+page.svelte @@ -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> | null = null + + const youtubeAuthenication = async (): Promise => { + 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() + }) + }
@@ -70,9 +94,11 @@ - +
+ +