diff --git a/README.MD b/README.MD index e309e54..59bac07 100644 --- a/README.MD +++ b/README.MD @@ -32,10 +32,6 @@ Run the server with `npm start`. If you want to make changes, run `npm run dev` to automatically reload the server and run tests whenever a file is saved. -# Privacy Policy - -If you set the `youtubeAPIKey` option in `config.json`, you must follow [Google's Privacy Policy](https://policies.google.com/privacy) and [YouTube's Terms of Service](https://www.youtube.com/t/terms) - # API Docs Available [here](https://github.com/ajayyy/SponsorBlock/wiki/API-Docs) diff --git a/config.json.example b/config.json.example index 0773e89..dbd009e 100644 --- a/config.json.example +++ b/config.json.example @@ -4,7 +4,7 @@ "port": 80, "globalSalt": "[global salt (pepper) that is added to every ip before hashing to make it even harder for someone to decode the ip]", "adminUserID": "[the hashed id of the user who can perform admin actions]", - "youtubeAPIKey": null, //get this from Google Cloud Platform [optional] + "newLeafURL": "http://localhost:3241", "discordReportChannelWebhookURL": null, //URL from discord if you would like notifications when someone makes a report [optional] "discordFirstTimeSubmissionsWebhookURL": null, //URL from discord if you would like notifications when someone makes a first time submission [optional] "discordCompletelyIncorrectReportWebhookURL": null, //URL from discord if you would like notifications when someone reports a submission as completely incorrect [optional] diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index d4987b2..197e65a 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -10,7 +10,7 @@ services: - ./database-export/:/opt/exports # To make this work, run chmod 777 ./database-exports ports: - 5432:5432 - restart: always + restart: unless-stopped redis: container_name: redis image: redis @@ -19,7 +19,15 @@ services: - ./redis/redis.conf:/usr/local/etc/redis/redis.conf ports: - 32773:6379 - restart: always + restart: unless-stopped + newleaf: + image: abeltramo/newleaf:latest + container_name: newleaf + restart: unless-stopped + ports: + - 3241:3000 + volumes: + - ./newleaf/configuration.py:/workdir/configuration.py volumes: database-data: diff --git a/docker/newleaf/configuration.py b/docker/newleaf/configuration.py new file mode 100644 index 0000000..d0b014c --- /dev/null +++ b/docker/newleaf/configuration.py @@ -0,0 +1,17 @@ +# ============================== +# You MUST set these settings. +# ============================== + +# A URL that this site can be accessed on. Do not include a trailing slash. +website_origin = "http://newleaf:3000" + + +# ============================== +# These settings are optional. +# ============================== + +# The address of the interface to bind to. +#bind_host = "0.0.0.0" + +# The port to bind to. +#bind_port = 3000 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 61b1181..4d3d560 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "license": "MIT", "dependencies": { + "abort-controller": "^3.0.0", "better-sqlite3": "^7.1.5", "dotenv": "^8.2.0", "express": "^4.17.1", @@ -19,8 +20,7 @@ "pg": "^8.5.1", "redis": "^3.1.1", "sync-mysql": "^3.0.1", - "uuid": "^3.3.2", - "youtube-api": "^3.0.1" + "uuid": "^3.3.2" }, "devDependencies": { "@types/better-sqlite3": "^5.4.0", @@ -294,33 +294,6 @@ "node": ">= 0.6" } }, - "node_modules/agent-base": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", - "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/agent-base/node_modules/debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/agent-base/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, "node_modules/ansi-align": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", @@ -456,14 +429,6 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, - "node_modules/arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", - "engines": { - "node": ">=8" - } - }, "node_modules/asap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/asap/-/asap-1.0.0.tgz", @@ -698,11 +663,6 @@ "ieee754": "^1.1.13" } }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" - }, "node_modules/buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -1122,14 +1082,6 @@ "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", "dev": true }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -1272,16 +1224,6 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, - "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-text-encoding": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz", - "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==" - }, "node_modules/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", @@ -1446,41 +1388,6 @@ "node": ">=0.10.0" } }, - "node_modules/gaxios": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.2.0.tgz", - "integrity": "sha512-+6WPeVzPvOshftpxJwRi2Ozez80tn/hdtOUag7+gajDHRJvAblKxTFSSMPtr2hmnLy7p0mvYz0rMXLBl8pSO7Q==", - "dependencies": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.3.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/gaxios/node_modules/is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/gcp-metadata": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.2.0.tgz", - "integrity": "sha512-vQZD57cQkqIA6YPGXM/zc+PIZfNRFdukWGsGZ5+LcJzesi5xp6Gn7a02wRJi4eXPyArNMIYpPET4QMxGqtlk6Q==", - "dependencies": { - "gaxios": "^3.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -1556,86 +1463,6 @@ "node": ">=4" } }, - "node_modules/google-auth-library": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.1.tgz", - "integrity": "sha512-0WfExOx3FrLYnY88RICQxvpaNzdwjz44OsHqHkIoAJfjY6Jck6CZRl1ASWadk+wbJ0LhkQ8rNY4zZebKml4Ghg==", - "dependencies": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^3.0.0", - "gcp-metadata": "^4.1.0", - "gtoken": "^5.0.4", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/google-auth-library/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/google-p12-pem": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.3.tgz", - "integrity": "sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==", - "dependencies": { - "node-forge": "^0.10.0" - }, - "bin": { - "gp12-pem": "build/src/bin/gp12-pem.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/googleapis": { - "version": "54.1.0", - "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-54.1.0.tgz", - "integrity": "sha512-xkBk3wRRYeJxEp9bVyAaYQM7qt3Eo3wBoznk+XnXbcSAjhl+M7icsUGb6VGNbZJfvpKAU7/tfhsed50pfs/jmA==", - "dependencies": { - "google-auth-library": "^6.0.0", - "googleapis-common": "^4.4.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/googleapis-common": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-4.4.1.tgz", - "integrity": "sha512-F1QcH8oU7TOuZex9p+XW7TeyLY0332NwBwJ3dZoN+51pXZXB5JjrKswrpgbhuREuIe8xAy8J1rlmFqxeP2mFfA==", - "dependencies": { - "extend": "^3.0.2", - "gaxios": "^3.2.0", - "google-auth-library": "^6.0.0", - "qs": "^6.7.0", - "url-template": "^2.0.8", - "uuid": "^8.0.0" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/googleapis-common/node_modules/uuid": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", - "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/got": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", @@ -1673,31 +1500,6 @@ "node": ">=4.x" } }, - "node_modules/gtoken": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.0.4.tgz", - "integrity": "sha512-U9wnSp4GZ7ov6zRdPuRHG4TuqEWqRRgT1gfXGNArhzBUn9byrPeH8uTmBWU/ZiWJJvTEmkjhDIC3mqHWdVi3xQ==", - "dependencies": { - "gaxios": "^3.0.0", - "google-p12-pem": "^3.0.3", - "jws": "^4.0.0", - "mime": "^2.2.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/gtoken/node_modules/mime": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -1741,34 +1543,6 @@ "node": ">= 0.6" } }, - "node_modules/https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/https-proxy-agent/node_modules/debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/https-proxy-agent/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -2021,39 +1795,12 @@ "js-yaml": "bin/js-yaml.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/just-extend": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz", "integrity": "sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==", "dev": true }, - "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/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/latest-version": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", @@ -2511,14 +2258,6 @@ "node": "4.x || >=6.0.0" } }, - "node_modules/node-forge": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/nodemon": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.2.tgz", @@ -3790,11 +3529,6 @@ "node": ">=0.10.0" } }, - "node_modules/url-template": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", - "integrity": "sha1-/FZaPMy/93MMd19WQflVV5FDnyE=" - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -4076,14 +3810,6 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } - }, - "node_modules/youtube-api": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/youtube-api/-/youtube-api-3.0.1.tgz", - "integrity": "sha512-r3N8xmE46desGsnTRD1KRiYnfPKElgp1BDMfeCkpyTMrE1qkTb8DxtJrQjEJxFpCE4s6xIQZa/cnWtTbOoNlkA==", - "dependencies": { - "googleapis": "^54.1.0" - } } }, "dependencies": { @@ -4334,29 +4060,6 @@ "negotiator": "0.6.2" } }, - "agent-base": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", - "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==", - "requires": { - "debug": "4" - }, - "dependencies": { - "debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, "ansi-align": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", @@ -4478,11 +4181,6 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, - "arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" - }, "asap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/asap/-/asap-1.0.0.tgz", @@ -4681,11 +4379,6 @@ "ieee754": "^1.1.13" } }, - "buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" - }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -5005,14 +4698,6 @@ "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", "dev": true }, - "ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -5133,16 +4818,6 @@ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-5.1.3.tgz", "integrity": "sha512-TINcxve5510pXj4n9/1AMupkj3iWxl3JuZaWhCdYDlZeoCPqweGZrxbrlqTCFb1CT5wli7s8e2SH/Qz2c9GorA==" }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "fast-text-encoding": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz", - "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==" - }, "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", @@ -5269,34 +4944,6 @@ } } }, - "gaxios": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.2.0.tgz", - "integrity": "sha512-+6WPeVzPvOshftpxJwRi2Ozez80tn/hdtOUag7+gajDHRJvAblKxTFSSMPtr2hmnLy7p0mvYz0rMXLBl8pSO7Q==", - "requires": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.3.0" - }, - "dependencies": { - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" - } - } - }, - "gcp-metadata": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.2.0.tgz", - "integrity": "sha512-vQZD57cQkqIA6YPGXM/zc+PIZfNRFdukWGsGZ5+LcJzesi5xp6Gn7a02wRJi4eXPyArNMIYpPET4QMxGqtlk6Q==", - "requires": { - "gaxios": "^3.0.0", - "json-bigint": "^1.0.0" - } - }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -5351,69 +4998,6 @@ "ini": "^1.3.4" } }, - "google-auth-library": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.1.tgz", - "integrity": "sha512-0WfExOx3FrLYnY88RICQxvpaNzdwjz44OsHqHkIoAJfjY6Jck6CZRl1ASWadk+wbJ0LhkQ8rNY4zZebKml4Ghg==", - "requires": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^3.0.0", - "gcp-metadata": "^4.1.0", - "gtoken": "^5.0.4", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - } - } - }, - "google-p12-pem": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.3.tgz", - "integrity": "sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==", - "requires": { - "node-forge": "^0.10.0" - } - }, - "googleapis": { - "version": "54.1.0", - "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-54.1.0.tgz", - "integrity": "sha512-xkBk3wRRYeJxEp9bVyAaYQM7qt3Eo3wBoznk+XnXbcSAjhl+M7icsUGb6VGNbZJfvpKAU7/tfhsed50pfs/jmA==", - "requires": { - "google-auth-library": "^6.0.0", - "googleapis-common": "^4.4.0" - } - }, - "googleapis-common": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-4.4.1.tgz", - "integrity": "sha512-F1QcH8oU7TOuZex9p+XW7TeyLY0332NwBwJ3dZoN+51pXZXB5JjrKswrpgbhuREuIe8xAy8J1rlmFqxeP2mFfA==", - "requires": { - "extend": "^3.0.2", - "gaxios": "^3.2.0", - "google-auth-library": "^6.0.0", - "qs": "^6.7.0", - "url-template": "^2.0.8", - "uuid": "^8.0.0" - }, - "dependencies": { - "uuid": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", - "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==" - } - } - }, "got": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", @@ -5445,24 +5029,6 @@ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, - "gtoken": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.0.4.tgz", - "integrity": "sha512-U9wnSp4GZ7ov6zRdPuRHG4TuqEWqRRgT1gfXGNArhzBUn9byrPeH8uTmBWU/ZiWJJvTEmkjhDIC3mqHWdVi3xQ==", - "requires": { - "gaxios": "^3.0.0", - "google-p12-pem": "^3.0.3", - "jws": "^4.0.0", - "mime": "^2.2.0" - }, - "dependencies": { - "mime": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" - } - } - }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -5497,30 +5063,6 @@ "toidentifier": "1.0.0" } }, - "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "requires": { - "agent-base": "6", - "debug": "4" - }, - "dependencies": { - "debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -5702,39 +5244,12 @@ "argparse": "^2.0.1" } }, - "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==", - "requires": { - "bignumber.js": "^9.0.0" - } - }, "just-extend": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz", "integrity": "sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==", "dev": true }, - "jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", - "requires": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - }, "latest-version": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", @@ -6083,11 +5598,6 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" }, - "node-forge": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" - }, "nodemon": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.2.tgz", @@ -7102,11 +6612,6 @@ "prepend-http": "^1.0.1" } }, - "url-template": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", - "integrity": "sha1-/FZaPMy/93MMd19WQflVV5FDnyE=" - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -7320,14 +6825,6 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true - }, - "youtube-api": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/youtube-api/-/youtube-api-3.0.1.tgz", - "integrity": "sha512-r3N8xmE46desGsnTRD1KRiYnfPKElgp1BDMfeCkpyTMrE1qkTb8DxtJrQjEJxFpCE4s6xIQZa/cnWtTbOoNlkA==", - "requires": { - "googleapis": "^54.1.0" - } } } } diff --git a/package.json b/package.json index 8e0d18b..214f242 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "author": "Ajay Ramachandran", "license": "MIT", "dependencies": { + "abort-controller": "^3.0.0", "better-sqlite3": "^7.1.5", "dotenv": "^8.2.0", "express": "^4.17.1", @@ -23,8 +24,7 @@ "pg": "^8.5.1", "redis": "^3.1.1", "sync-mysql": "^3.0.1", - "uuid": "^3.3.2", - "youtube-api": "^3.0.1" + "uuid": "^3.3.2" }, "devDependencies": { "@types/better-sqlite3": "^5.4.0", diff --git a/src/config.ts b/src/config.ts index a684ba7..2f6d774 100644 --- a/src/config.ts +++ b/src/config.ts @@ -44,7 +44,7 @@ addDefaults(config, { }, }, userCounterURL: null, - youtubeAPIKey: null, + newLeafURL: null, maxRewardTimePerSegmentInSeconds: 86400, postgres: null, dumpDatabase: { diff --git a/src/routes/postSkipSegments.ts b/src/routes/postSkipSegments.ts index cf09762..bae0692 100644 --- a/src/routes/postSkipSegments.ts +++ b/src/routes/postSkipSegments.ts @@ -1,7 +1,7 @@ import {config} from '../config'; import {Logger} from '../utils/logger'; import {db, privateDB} from '../databases/databases'; -import {YouTubeAPI} from '../utils/youtubeApi'; +import {getMaxResThumbnail, YouTubeAPI} from '../utils/youtubeApi'; import {getSubmissionUUID} from '../utils/getSubmissionUUID'; import fetch from 'node-fetch'; import isoDurations, { end } from 'iso8601-duration'; @@ -18,16 +18,11 @@ import { deleteLockCategories } from './deleteLockCategories'; import { getCategoryActionType } from '../utils/categoryInfo'; import { QueryCacher } from '../utils/queryCacher'; import { getReputation } from '../utils/reputation'; +import { APIVideoData, APIVideoInfo } from '../types/youtubeApi.model'; -interface APIVideoInfo { - err: string | boolean, - data?: any -} - -async function sendWebhookNotification(userID: string, videoID: string, UUID: string, submissionCount: number, youtubeData: any, {submissionStart, submissionEnd}: { submissionStart: number; submissionEnd: number; }, segmentInfo: any) { +async function sendWebhookNotification(userID: string, videoID: string, UUID: string, submissionCount: number, youtubeData: APIVideoData, {submissionStart, submissionEnd}: { submissionStart: number; submissionEnd: number; }, segmentInfo: any) { const row = await db.prepare('get', `SELECT "userName" FROM "userNames" WHERE "userID" = ?`, [userID]); const userName = row !== undefined ? row.userName : null; - const video = youtubeData.items[0]; let scopeName = "submissions.other"; if (submissionCount <= 1) { @@ -37,8 +32,8 @@ async function sendWebhookNotification(userID: string, videoID: string, UUID: st dispatchEvent(scopeName, { "video": { "id": videoID, - "title": video.snippet.title, - "thumbnail": video.snippet.thumbnails.maxres ? video.snippet.thumbnails.maxres : null, + "title": youtubeData?.title, + "thumbnail": getMaxResThumbnail(youtubeData) || null, "url": "https://www.youtube.com/watch?v=" + videoID, }, "submission": { @@ -76,7 +71,7 @@ async function sendWebhooks(apiVideoInfo: APIVideoInfo, userID: string, videoID: method: 'POST', body: JSON.stringify({ "embeds": [{ - "title": data.items[0].snippet.title, + "title": data?.title, "url": "https://www.youtube.com/watch?v=" + videoID + "&t=" + (parseInt(startTime.toFixed(0)) - 2), "description": "Submission ID: " + UUID + "\n\nTimestamp: " + @@ -87,7 +82,7 @@ async function sendWebhooks(apiVideoInfo: APIVideoInfo, userID: string, videoID: "name": userID, }, "thumbnail": { - "url": data.items[0].snippet.thumbnails.maxres ? data.items[0].snippet.thumbnails.maxres.url : "", + "url": getMaxResThumbnail(data) || "", }, }], }), @@ -177,10 +172,7 @@ async function autoModerateSubmission(apiVideoInfo: APIVideoInfo, const {err, data} = apiVideoInfo; if (err) return false; - // Check to see if video exists - if (data.pageInfo.totalResults === 0) return "No video exists with id " + submission.videoID; - - const duration = getYouTubeVideoDuration(apiVideoInfo); + const duration = apiVideoInfo?.data?.lengthSeconds; const segments = submission.segments; let nbString = ""; for (let i = 0; i < segments.length; i++) { @@ -220,8 +212,7 @@ async function autoModerateSubmission(apiVideoInfo: APIVideoInfo, return a[0] - b[0] || a[1] - b[1]; })); - let videoDuration = data.items[0].contentDetails.duration; - videoDuration = isoDurations.toSeconds(isoDurations.parse(videoDuration)); + const videoDuration = data.lengthSeconds; if (videoDuration != 0) { let allSegmentDuration = 0; //sum all segment times together @@ -273,13 +264,8 @@ async function autoModerateSubmission(apiVideoInfo: APIVideoInfo, } } -function getYouTubeVideoDuration(apiVideoInfo: APIVideoInfo): VideoDuration { - const duration = apiVideoInfo?.data?.items[0]?.contentDetails?.duration; - return duration ? isoDurations.toSeconds(isoDurations.parse(duration)) as VideoDuration : null; -} - async function getYouTubeVideoInfo(videoID: VideoID, ignoreCache = false): Promise { - if (config.youtubeAPIKey !== null) { + if (config.newLeafURL !== null) { return YouTubeAPI.listVideos(videoID, ignoreCache); } else { return null; @@ -375,7 +361,7 @@ export async function postSkipSegments(req: Request, res: Response) { // Don't use cache if we don't know the video duraton, or the client claims that it has changed apiVideoInfo = await getYouTubeVideoInfo(videoID, !videoDuration || videoDurationChanged(videoDuration)); } - const apiVideoDuration = getYouTubeVideoDuration(apiVideoInfo); + const apiVideoDuration = apiVideoInfo?.data?.lengthSeconds as VideoDuration; if (!videoDuration || (apiVideoDuration && Math.abs(videoDuration - apiVideoDuration) > 2)) { // If api duration is far off, take that one instead (it is only precise to seconds, not millis) videoDuration = apiVideoDuration || 0 as VideoDuration; diff --git a/src/routes/voteOnSponsorTime.ts b/src/routes/voteOnSponsorTime.ts index 25a331f..2ae0a4c 100644 --- a/src/routes/voteOnSponsorTime.ts +++ b/src/routes/voteOnSponsorTime.ts @@ -2,7 +2,7 @@ import {Request, Response} from 'express'; import {Logger} from '../utils/logger'; import {isUserVIP} from '../utils/isUserVIP'; import fetch from 'node-fetch'; -import {YouTubeAPI} from '../utils/youtubeApi'; +import {getMaxResThumbnail, YouTubeAPI} from '../utils/youtubeApi'; import {db, privateDB} from '../databases/databases'; import {dispatchEvent, getVoteAuthor, getVoteAuthorRaw} from '../utils/webhookUtils'; import {getFormattedTime} from '../utils/getFormattedTime'; @@ -57,11 +57,11 @@ async function sendWebhooks(voteData: VoteData) { webhookURL = config.discordCompletelyIncorrectReportWebhookURL; } - if (config.youtubeAPIKey !== null) { + if (config.newLeafURL !== null) { const { err, data } = await YouTubeAPI.listVideos(submissionInfoRow.videoID); - if (err || data.items.length === 0) { - if (err) Logger.error(err.toString()); + if (err) { + Logger.error(err.toString()); return; } const isUpvote = voteData.incrementAmount > 0; @@ -72,9 +72,9 @@ async function sendWebhooks(voteData: VoteData) { }, "video": { "id": submissionInfoRow.videoID, - "title": data.items[0].snippet.title, + "title": data?.title, "url": "https://www.youtube.com/watch?v=" + submissionInfoRow.videoID, - "thumbnail": data.items[0].snippet.thumbnails.maxres ? data.items[0].snippet.thumbnails.maxres.url : "", + "thumbnail": getMaxResThumbnail(data) || null, }, "submission": { "UUID": voteData.UUID, @@ -103,7 +103,7 @@ async function sendWebhooks(voteData: VoteData) { method: 'POST', body: JSON.stringify({ "embeds": [{ - "title": data.items[0].snippet.title, + "title": data?.title, "url": "https://www.youtube.com/watch?v=" + submissionInfoRow.videoID + "&t=" + (submissionInfoRow.startTime.toFixed(0) - 2), "description": "**" + voteData.row.votes + " Votes Prior | " + @@ -120,7 +120,7 @@ async function sendWebhooks(voteData: VoteData) { "name": voteData.finalResponse?.finalMessage ?? getVoteAuthor(userSubmissionCountRow.submissionCount, voteData.isVIP, voteData.isOwnSubmission), }, "thumbnail": { - "url": data.items[0].snippet.thumbnails.maxres ? data.items[0].snippet.thumbnails.maxres.url : "", + "url": getMaxResThumbnail(data) || "", }, }], }), diff --git a/src/types/config.model.ts b/src/types/config.model.ts index 117c732..aa0a863 100644 --- a/src/types/config.model.ts +++ b/src/types/config.model.ts @@ -6,7 +6,7 @@ export interface SBSConfig { mockPort?: number; globalSalt: string; adminUserID: string; - youtubeAPIKey?: string; + newLeafURL?: string; discordReportChannelWebhookURL?: string; discordFirstTimeSubmissionsWebhookURL?: string; discordCompletelyIncorrectReportWebhookURL?: string; diff --git a/src/types/youtubeApi.model.ts b/src/types/youtubeApi.model.ts new file mode 100644 index 0000000..5e869b3 --- /dev/null +++ b/src/types/youtubeApi.model.ts @@ -0,0 +1,111 @@ +export interface APIVideoData { + "title": string, + "videoId": string, + "videoThumbnails": [ + { + "quality": string, + "url": string, + second__originalUrl: string, + "width": number, + "height": number + } + ], + + "description": string, + "descriptionHtml": string, + "published": number, + "publishedText": string, + + "keywords": string[], + "viewCount": number, + "likeCount": number, + "dislikeCount": number, + + "paid": boolean, + "premium": boolean, + "isFamilyFriendly": boolean, + "allowedRegions": string[], + "genre": string, + "genreUrl": string, + + "author": string, + "authorId": string, + "authorUrl": string, + "authorThumbnails": [ + { + "url": string, + "width": number, + "height": number + } + ], + + "subCountText": string, + "lengthSeconds": number, + "allowRatings": boolean, + "rating": number, + "isListed": boolean, + "liveNow": boolean, + "isUpcoming": boolean, + "premiereTimestamp"?: number, + + "hlsUrl"?: string, + "adaptiveFormats": [ + { + "index": string, + "bitrate": string, + "init": string, + "url": string, + "itag": string, + "type": string, + "clen": string, + "lmt": string, + "projectionType": number, + "container": string, + "encoding": string, + "qualityLabel"?: string, + "resolution"?: string + } + ], + "formatStreams": [ + { + "url": string, + "itag": string, + "type": string, + "quality": string, + "container": string, + "encoding": string, + "qualityLabel": string, + "resolution": string, + "size": string + } + ], + "captions": [ + { + "label": string, + "languageCode": string, + "url": string + } + ], + "recommendedVideos": [ + { + "videoId": string, + "title": string, + "videoThumbnails": [ + { + "quality": string, + "url": string, + "width": number, + "height": number + } + ], + "author": string, + "lengthSeconds": number, + "viewCountText": string + } + ] +} + +export interface APIVideoInfo { + err: string | boolean, + data?: APIVideoData +} \ No newline at end of file diff --git a/src/utils/youtubeApi.ts b/src/utils/youtubeApi.ts index d02665b..95ef422 100644 --- a/src/utils/youtubeApi.ts +++ b/src/utils/youtubeApi.ts @@ -1,22 +1,16 @@ +import fetch from 'node-fetch'; import {config} from '../config'; import {Logger} from './logger'; import redis from './redis'; -// @ts-ignore -import _youTubeAPI from 'youtube-api'; - -_youTubeAPI.authenticate({ - type: "key", - key: config.youtubeAPIKey, -}); +import { APIVideoData, APIVideoInfo } from '../types/youtubeApi.model'; export class YouTubeAPI { - static async listVideos(videoID: string, ignoreCache = false): Promise<{err: string | boolean, data?: any}> { - const part = 'contentDetails,snippet'; + static async listVideos(videoID: string, ignoreCache = false): Promise { if (!videoID || videoID.length !== 11 || videoID.includes(".")) { return { err: "Invalid video ID" }; } - const redisKey = "youtube.video." + videoID; + const redisKey = "yt.newleaf.video." + videoID; if (!ignoreCache) { const {err, reply} = await redis.getAsync(redisKey); @@ -25,34 +19,37 @@ export class YouTubeAPI { return { err: err?.message, data: JSON.parse(reply) } } - } + } + + if (!config.newLeafURL) return {err: "NewLeaf URL not found", data: null}; try { - const { ytErr, data } = await new Promise((resolve) => _youTubeAPI.videos.list({ - part, - id: videoID, - }, (ytErr: boolean | string, result: any) => resolve({ytErr, data: result?.data}))); + const result = await fetch(config.newLeafURL + "/api/v1/videos/" + videoID, { method: "GET" }); - if (!ytErr) { - // Only set cache if data returned - if (data.items.length > 0) { - const { err: setErr } = await redis.setAsync(redisKey, JSON.stringify(data)); + if (result.ok) { + const data = await result.json(); + if (data.error) { + return { err: data.err, data: null }; + } - if (setErr) { - Logger.warn(setErr.message); + redis.setAsync(redisKey, JSON.stringify(data)).then((result) => { + if (result?.err) { + Logger.warn(result?.err.message); } else { Logger.debug("redis: video information cache set for: " + videoID); } - - return { err: false, data }; // don't fail - } else { - return { err: false, data }; // don't fail - } + }); + + return { err: false, data }; } else { - return { err: ytErr, data }; + return { err: result.statusText, data: null }; } } catch (err) { return {err, data: null} } } } + +export function getMaxResThumbnail(apiInfo: APIVideoData): string | void { + return apiInfo?.videoThumbnails?.find((elem) => elem.quality === "maxres")?.second__originalUrl; +} \ No newline at end of file diff --git a/test.json b/test.json index 30958ae..c3b1096 100644 --- a/test.json +++ b/test.json @@ -3,7 +3,7 @@ "mockPort": 8081, "globalSalt": "testSalt", "adminUserID": "testUserId", - "youtubeAPIKey": "", + "newLeafURL": "placeholder", "discordReportChannelWebhookURL": "http://127.0.0.1:8081/ReportChannelWebhook", "discordFirstTimeSubmissionsWebhookURL": "http://127.0.0.1:8081/FirstTimeSubmissionsWebhook", "discordCompletelyIncorrectReportWebhookURL": "http://127.0.0.1:8081/CompletelyIncorrectReportWebhook", diff --git a/test/cases/postSkipSegments.ts b/test/cases/postSkipSegments.ts index ce22506..ec5ccf3 100644 --- a/test/cases/postSkipSegments.ts +++ b/test/cases/postSkipSegments.ts @@ -118,7 +118,7 @@ describe('postSkipSegments', () => { .then(async res => { if (res.status === 200) { const row = await db.prepare('get', `SELECT "startTime", "endTime", "locked", "category", "videoDuration" FROM "sponsorTimes" WHERE "videoID" = ?`, ["dQw4w9WgXZX"]); - if (row.startTime === 0 && row.endTime === 10 && row.locked === 0 && row.category === "sponsor" && row.videoDuration === 5010) { + if (row.startTime === 0 && row.endTime === 10 && row.locked === 0 && row.category === "sponsor" && row.videoDuration === 4980) { done(); } else { done("Submitted times were not saved. Actual submission: " + JSON.stringify(row)); @@ -140,7 +140,7 @@ describe('postSkipSegments', () => { body: JSON.stringify({ userID: "test", videoID: "dQw4w9WgXZH", - videoDuration: 5010.20, + videoDuration: 4980.20, segments: [{ segment: [1, 10], category: "sponsor", @@ -150,7 +150,7 @@ describe('postSkipSegments', () => { .then(async res => { if (res.status === 200) { const row = await db.prepare('get', `SELECT "startTime", "endTime", "locked", "category", "videoDuration" FROM "sponsorTimes" WHERE "videoID" = ?`, ["dQw4w9WgXZH"]); - if (row.startTime === 1 && row.endTime === 10 && row.locked === 0 && row.category === "sponsor" && row.videoDuration === 5010.20) { + if (row.startTime === 1 && row.endTime === 10 && row.locked === 0 && row.category === "sponsor" && row.videoDuration === 4980.20) { done(); } else { done("Submitted times were not saved. Actual submission: " + JSON.stringify(row)); @@ -237,6 +237,21 @@ describe('postSkipSegments', () => { } }); + it('Should still not be allowed if youtube thinks duration is 0', (done: Done) => { + fetch(getbaseURL() + + "/api/postVideoSponsorTimes?videoID=noDuration&startTime=30&endTime=10000&userID=testing", { + method: 'POST', + }) + .then(async res => { + if (res.status === 403) done(); // pass + else { + const body = await res.text(); + done("non 403 status code: " + res.status + " (" + body + ")"); + } + }) + .catch(err => done("Couldn't call endpoint")); + }); + it('Should be able to submit a single time under a different service (JSON method)', (done: Done) => { fetch(getbaseURL() + "/api/postVideoSponsorTimes", { @@ -666,34 +681,6 @@ describe('postSkipSegments', () => { .catch(err => done(err)); }); - it('Should be allowed if youtube thinks duration is 0', (done: Done) => { - fetch(getbaseURL() - + "/api/postVideoSponsorTimes?videoID=noDuration&startTime=30&endTime=10000&userID=testing", { - method: 'POST', - }) - .then(async res => { - if (res.status === 200) done(); // pass - else { - const body = await res.text(); - done("non 200 status code: " + res.status + " (" + body + ")"); - } - }) - .catch(err => done("Couldn't call endpoint")); - }); - - it('Should be rejected if not a valid videoID', (done: Done) => { - fetch(getbaseURL() - + "/api/postVideoSponsorTimes?videoID=knownWrongID&startTime=30&endTime=1000000&userID=testing") - .then(async res => { - if (res.status === 403) done(); // pass - else { - const body = await res.text(); - done("non 403 status code: " + res.status + " (" + body + ")"); - } - }) - .catch(err => done("Couldn't call endpoint")); - }); - it('Should return 400 for missing params (Params method)', (done: Done) => { fetch(getbaseURL() + "/api/postVideoSponsorTimes?startTime=9&endTime=10&userID=test", { diff --git a/test/youtubeMock.ts b/test/youtubeMock.ts index de7e17e..3e68369 100644 --- a/test/youtubeMock.ts +++ b/test/youtubeMock.ts @@ -1,28 +1,14 @@ -/* -YouTubeAPI.videos.list({ - part: "snippet", - id: videoID -}, function (err, data) {}); - */ - -// https://developers.google.com/youtube/v3/docs/videos - +import { APIVideoData, APIVideoInfo } from "../src/types/youtubeApi.model"; export class YouTubeApiMock { - static async listVideos(videoID: string, ignoreCache = false): Promise<{err: string | boolean, data?: any}> { + static async listVideos(videoID: string, ignoreCache = false): Promise { const obj = { id: videoID }; if (obj.id === "knownWrongID") { return { - err: null, - data: { - pageInfo: { - totalResults: 0, - }, - items: [], - } + err: "No video found" }; } @@ -30,49 +16,35 @@ export class YouTubeApiMock { return { err: null, data: { - pageInfo: { - totalResults: 1, - }, - items: [ + title: "Example Title", + lengthSeconds: 0, + videoThumbnails: [ { - contentDetails: { - duration: "PT0S", - }, - snippet: { - title: "Example Title", - thumbnails: { - maxres: { - url: "https://sponsor.ajay.app/LogoSponsorBlockSimple256px.png", - }, - }, - }, + quality: "maxres", + url: "https://sponsor.ajay.app/LogoSponsorBlockSimple256px.png", + second__originalUrl:"https://sponsor.ajay.app/LogoSponsorBlockSimple256px.png", + width: 1280, + height: 720 }, - ], - } + ] + } as APIVideoData }; } else { return { err: null, data: { - pageInfo: { - totalResults: 1, - }, - items: [ + title: "Example Title", + lengthSeconds: 4980, + videoThumbnails: [ { - contentDetails: { - duration: "PT1H23M30S", - }, - snippet: { - title: "Example Title", - thumbnails: { - maxres: { - url: "https://sponsor.ajay.app/LogoSponsorBlockSimple256px.png", - }, - }, - }, + quality: "maxres", + url: "https://sponsor.ajay.app/LogoSponsorBlockSimple256px.png", + second__originalUrl:"https://sponsor.ajay.app/LogoSponsorBlockSimple256px.png", + width: 1280, + height: 720 }, - ], - } + ] + } as APIVideoData }; } }