From 08d27265fc8ba27f5bf45035f37a194c7a6bcdf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dainius=20Dauk=C5=A1evi=C4=8Dius?= Date: Sat, 17 Oct 2020 21:56:54 +0300 Subject: [PATCH] migrate to typescript --- .editorconfig | 17 + .gitignore | 3 +- Dockerfile | 2 +- entrypoint.sh | 2 +- index.js | 6 - index.ts | 9 + package-lock.json | 1082 +++++++++-------- package.json | 21 +- src/{app.js => app.ts} | 84 +- src/config.js | 37 - src/config.ts | 58 + src/databases/IDatabase.ts | 13 + src/databases/Mysql.js | 28 - src/databases/Mysql.ts | 49 + src/databases/Sqlite.js | 33 - src/databases/Sqlite.ts | 107 ++ src/databases/databases.js | 81 -- src/databases/databases.ts | 47 + src/middleware/cors.js | 5 - src/middleware/cors.ts | 7 + src/middleware/logger.js | 6 - src/middleware/logger.ts | 7 + src/middleware/requestRateLimit.js | 18 - src/middleware/requestRateLimit.ts | 21 + src/middleware/userCounter.js | 13 - src/middleware/userCounter.ts | 13 + .../{addUserAsVIP.js => addUserAsVIP.ts} | 28 +- ...eleteNoSegments.js => deleteNoSegments.ts} | 34 +- src/routes/getDaysSavedFormatted.js | 12 - src/routes/getDaysSavedFormatted.ts | 13 + .../{getIsUserVIP.js => getIsUserVIP.ts} | 17 +- src/routes/getSavedTimeForUser.js | 32 - src/routes/getSavedTimeForUser.ts | 33 + ...{getSkipSegments.js => getSkipSegments.ts} | 77 +- ...entsByHash.js => getSkipSegmentsByHash.ts} | 28 +- src/routes/getTopUsers.js | 92 -- src/routes/getTopUsers.ts | 92 ++ .../{getTotalStats.js => getTotalStats.ts} | 23 +- src/routes/getUserInfo.js | 82 -- src/routes/getUserInfo.ts | 84 ++ src/routes/{getUsername.js => getUsername.ts} | 20 +- src/routes/getViewsForUser.js | 33 - src/routes/getViewsForUser.ts | 35 + ...sorTimes.js => oldGetVideoSponsorTimes.ts} | 16 +- src/routes/oldSubmitSponsorTimes.js | 7 - src/routes/oldSubmitSponsorTimes.ts | 8 + .../{postNoSegments.js => postNoSegments.ts} | 37 +- src/routes/postSegmentShift.js | 101 -- src/routes/postSegmentShift.ts | 101 ++ ...ostSkipSegments.js => postSkipSegments.ts} | 312 ++--- src/routes/{postWarning.js => postWarning.ts} | 17 +- src/routes/setUsername.js | 60 - src/routes/setUsername.ts | 58 + src/routes/shadowBanUser.js | 92 -- src/routes/shadowBanUser.ts | 89 ++ src/routes/viewedVideoSponsorTime.js | 16 - src/routes/viewedVideoSponsorTime.ts | 16 + ...eOnSponsorTime.js => voteOnSponsorTime.ts} | 227 ++-- src/types/config.model.ts | 52 + src/utils/createMemoryCache.js | 44 - src/utils/createMemoryCache.ts | 43 + src/utils/getFormattedTime.js | 14 - src/utils/getFormattedTime.ts | 14 + src/utils/getHash.js | 12 - src/utils/getHash.ts | 12 + src/utils/getIP.js | 16 - src/utils/getIP.ts | 19 + src/utils/getSubmissionUUID.js | 7 - src/utils/getSubmissionUUID.ts | 5 + ...ashPrefixTester.js => hashPrefixTester.ts} | 6 +- src/utils/isUserTrustworthy.js | 20 - src/utils/isUserTrustworthy.ts | 20 + src/utils/{isUserVIP.js => isUserVIP.ts} | 5 +- src/utils/logger.js | 70 -- src/utils/logger.ts | 89 ++ src/utils/redis.js | 18 - src/utils/redis.ts | 19 + src/utils/webhookUtils.js | 52 - src/utils/webhookUtils.ts | 56 + src/utils/youtubeAPI.js | 62 - src/utils/youtubeApi.ts | 69 ++ test.js | 41 - test.ts | 44 + test/cases/dbUpgrade.js | 12 - test/cases/dbUpgrade.ts | 11 + test/cases/getHash.js | 33 - test/cases/getHash.ts | 33 + test/cases/getIsUserVIP.js | 57 - test/cases/getIsUserVIP.ts | 57 + test/cases/getSavedTimeForUser.js | 21 - test/cases/getSavedTimeForUser.ts | 21 + test/cases/getSegmentsByHash.js | 190 --- test/cases/getSegmentsByHash.ts | 192 +++ test/cases/getSkipSegments.js | 217 ---- test/cases/getSkipSegments.ts | 216 ++++ test/cases/getSubmissionUUID.js | 8 - test/cases/getSubmissionUUID.ts | 8 + test/cases/getUserInfo.js | 167 --- test/cases/getUserInfo.ts | 167 +++ test/cases/noSegmentRecords.js | 466 ------- test/cases/noSegmentRecords.ts | 463 +++++++ test/cases/oldGetSponsorTime.js | 88 -- test/cases/oldGetSponsorTime.ts | 87 ++ test/cases/oldSubmitSponsorTimes.js | 55 - test/cases/oldSubmitSponsorTimes.ts | 52 + test/cases/postSkipSegments.js | 455 ------- test/cases/postSkipSegments.ts | 451 +++++++ test/cases/postWarning.js | 47 - test/cases/postWarning.ts | 47 + test/cases/segmentShift.js | 273 ----- test/cases/segmentShift.ts | 273 +++++ test/cases/voteOnSponsorTime.js | 367 ------ test/cases/voteOnSponsorTime.ts | 367 ++++++ test/mocks.js | 51 - test/mocks.ts | 51 + test/utils.js | 7 - test/utils.ts | 10 + test/youtubeMock.js | 72 -- test/youtubeMock.ts | 71 ++ tsconfig.json | 80 ++ 120 files changed, 5002 insertions(+), 4711 deletions(-) create mode 100644 .editorconfig delete mode 100644 index.js create mode 100644 index.ts rename src/{app.js => app.ts} (55%) delete mode 100644 src/config.js create mode 100644 src/config.ts create mode 100644 src/databases/IDatabase.ts delete mode 100644 src/databases/Mysql.js create mode 100644 src/databases/Mysql.ts delete mode 100644 src/databases/Sqlite.js create mode 100644 src/databases/Sqlite.ts delete mode 100644 src/databases/databases.js create mode 100644 src/databases/databases.ts delete mode 100644 src/middleware/cors.js create mode 100644 src/middleware/cors.ts delete mode 100644 src/middleware/logger.js create mode 100644 src/middleware/logger.ts delete mode 100644 src/middleware/requestRateLimit.js create mode 100644 src/middleware/requestRateLimit.ts delete mode 100644 src/middleware/userCounter.js create mode 100644 src/middleware/userCounter.ts rename src/routes/{addUserAsVIP.js => addUserAsVIP.ts} (57%) rename src/routes/{deleteNoSegments.js => deleteNoSegments.ts} (52%) delete mode 100644 src/routes/getDaysSavedFormatted.js create mode 100644 src/routes/getDaysSavedFormatted.ts rename src/routes/{getIsUserVIP.js => getIsUserVIP.ts} (52%) delete mode 100644 src/routes/getSavedTimeForUser.js create mode 100644 src/routes/getSavedTimeForUser.ts rename src/routes/{getSkipSegments.js => getSkipSegments.ts} (77%) rename src/routes/{getSkipSegmentsByHash.js => getSkipSegmentsByHash.ts} (52%) delete mode 100644 src/routes/getTopUsers.js create mode 100644 src/routes/getTopUsers.ts rename src/routes/{getTotalStats.js => getTotalStats.ts} (79%) delete mode 100644 src/routes/getUserInfo.js create mode 100644 src/routes/getUserInfo.ts rename src/routes/{getUsername.js => getUsername.ts} (59%) delete mode 100644 src/routes/getViewsForUser.js create mode 100644 src/routes/getViewsForUser.ts rename src/routes/{oldGetVideoSponsorTimes.js => oldGetVideoSponsorTimes.ts} (59%) delete mode 100644 src/routes/oldSubmitSponsorTimes.js create mode 100644 src/routes/oldSubmitSponsorTimes.ts rename src/routes/{postNoSegments.js => postNoSegments.ts} (70%) delete mode 100644 src/routes/postSegmentShift.js create mode 100644 src/routes/postSegmentShift.ts rename src/routes/{postSkipSegments.js => postSkipSegments.ts} (58%) rename src/routes/{postWarning.js => postWarning.ts} (57%) delete mode 100644 src/routes/setUsername.js create mode 100644 src/routes/setUsername.ts delete mode 100644 src/routes/shadowBanUser.js create mode 100644 src/routes/shadowBanUser.ts delete mode 100644 src/routes/viewedVideoSponsorTime.js create mode 100644 src/routes/viewedVideoSponsorTime.ts rename src/routes/{voteOnSponsorTime.js => voteOnSponsorTime.ts} (66%) create mode 100644 src/types/config.model.ts delete mode 100644 src/utils/createMemoryCache.js create mode 100644 src/utils/createMemoryCache.ts delete mode 100644 src/utils/getFormattedTime.js create mode 100644 src/utils/getFormattedTime.ts delete mode 100644 src/utils/getHash.js create mode 100644 src/utils/getHash.ts delete mode 100644 src/utils/getIP.js create mode 100644 src/utils/getIP.ts delete mode 100644 src/utils/getSubmissionUUID.js create mode 100644 src/utils/getSubmissionUUID.ts rename src/utils/{hashPrefixTester.js => hashPrefixTester.ts} (73%) delete mode 100644 src/utils/isUserTrustworthy.js create mode 100644 src/utils/isUserTrustworthy.ts rename src/utils/{isUserVIP.js => isUserVIP.ts} (52%) delete mode 100644 src/utils/logger.js create mode 100644 src/utils/logger.ts delete mode 100644 src/utils/redis.js create mode 100644 src/utils/redis.ts delete mode 100644 src/utils/webhookUtils.js create mode 100644 src/utils/webhookUtils.ts delete mode 100644 src/utils/youtubeAPI.js create mode 100644 src/utils/youtubeApi.ts delete mode 100644 test.js create mode 100644 test.ts delete mode 100644 test/cases/dbUpgrade.js create mode 100644 test/cases/dbUpgrade.ts delete mode 100644 test/cases/getHash.js create mode 100644 test/cases/getHash.ts delete mode 100644 test/cases/getIsUserVIP.js create mode 100644 test/cases/getIsUserVIP.ts delete mode 100644 test/cases/getSavedTimeForUser.js create mode 100644 test/cases/getSavedTimeForUser.ts delete mode 100644 test/cases/getSegmentsByHash.js create mode 100644 test/cases/getSegmentsByHash.ts delete mode 100644 test/cases/getSkipSegments.js create mode 100644 test/cases/getSkipSegments.ts delete mode 100644 test/cases/getSubmissionUUID.js create mode 100644 test/cases/getSubmissionUUID.ts delete mode 100644 test/cases/getUserInfo.js create mode 100644 test/cases/getUserInfo.ts delete mode 100644 test/cases/noSegmentRecords.js create mode 100644 test/cases/noSegmentRecords.ts delete mode 100644 test/cases/oldGetSponsorTime.js create mode 100644 test/cases/oldGetSponsorTime.ts delete mode 100644 test/cases/oldSubmitSponsorTimes.js create mode 100644 test/cases/oldSubmitSponsorTimes.ts delete mode 100644 test/cases/postSkipSegments.js create mode 100644 test/cases/postSkipSegments.ts delete mode 100644 test/cases/postWarning.js create mode 100644 test/cases/postWarning.ts delete mode 100644 test/cases/segmentShift.js create mode 100644 test/cases/segmentShift.ts delete mode 100644 test/cases/voteOnSponsorTime.js create mode 100644 test/cases/voteOnSponsorTime.ts delete mode 100644 test/mocks.js create mode 100644 test/mocks.ts delete mode 100644 test/utils.js create mode 100644 test/utils.ts delete mode 100644 test/youtubeMock.js create mode 100644 test/youtubeMock.ts create mode 100644 tsconfig.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ace6a45 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true + +[*.{js,json,ts,tsx}] +charset = utf-8 +indent_style = space +indent_size = 4 + +[package.json] +indent_size = 2 diff --git a/.gitignore b/.gitignore index 3362a8d..3715c67 100644 --- a/.gitignore +++ b/.gitignore @@ -102,4 +102,5 @@ test/databases/private.db config.json # Mac files -.DS_Store \ No newline at end of file +.DS_Store +/.idea/ diff --git a/Dockerfile b/Dockerfile index ea7d751..4dc9470 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM node:12 WORKDIR /usr/src/app COPY package.json . RUN npm install -COPY index.js . +COPY index.ts . COPY src src RUN mkdir databases COPY databases/*.sql databases/ diff --git a/entrypoint.sh b/entrypoint.sh index 6215525..31b838c 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -23,4 +23,4 @@ cp /etc/sponsorblock/config.json . || cat < config.json "readOnly": false } EOF -node index.js \ No newline at end of file +ts-node index.ts diff --git a/index.js b/index.js deleted file mode 100644 index a877bab..0000000 --- a/index.js +++ /dev/null @@ -1,6 +0,0 @@ -var config = require('./src/config.js'); -var createServer = require('./src/app.js'); -const logger = require('./src/utils/logger.js'); -var server = createServer(() => { - logger.info("Server started on port " + config.port + "."); -}); diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..b41e572 --- /dev/null +++ b/index.ts @@ -0,0 +1,9 @@ +import {config} from "./src/config"; +import {initDb} from './src/databases/databases'; +import {createServer} from "./src/app"; +import {Logger} from "./src/utils/logger"; + +initDb(); +createServer(() => { + Logger.info("Server started on port " + config.port + "."); +}); diff --git a/package-lock.json b/package-lock.json index c608133..3f5ebe1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,12 +4,182 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@types/better-sqlite3": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-5.4.0.tgz", + "integrity": "sha512-nzm7lJ7l3jBmGUbtkL8cdOMhPkN6Pw2IM+b0V7iIKba+YKiLrjkIy7vVLsBIVnd7+lgzBzrHsXZxCaFTcmw5Ow==", + "dev": true, + "requires": { + "@types/integer": "*" + } + }, + "@types/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/caseless": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==", + "dev": true + }, + "@types/connect": { + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", + "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/express": { + "version": "4.17.8", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.8.tgz", + "integrity": "sha512-wLhcKh3PMlyA2cNAB9sjM1BntnhPMiM0JOBwPBqttjHev2428MLEB4AYVN+d8s2iyCVZac+o41Pflm/ZH5vLXQ==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-rate-limit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/express-rate-limit/-/express-rate-limit-5.1.0.tgz", + "integrity": "sha512-vmg7S3hUnfFmp06V01DrTB41mbQYXMV/F4aF5KKnfCIeSlnizatXaqO9UgR6LvNEEd3eMpuUTLxR6nv3d4hZ6g==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.13.tgz", + "integrity": "sha512-RgDi5a4nuzam073lRGKTUIaL3eF2+H7LJvJ8eUnCI0wA6SNjXc44DCmWNiTLs/AZ7QlsFWZiw/gTG3nSQGL0fA==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "@types/integer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/integer/-/integer-1.0.1.tgz", + "integrity": "sha512-DmZDpSVnsuBrOhtHwE1oKmUJ3qVjHhhNQ7WnZy9/RhH3A24Ar+9o4SoaCWcTzQhalpRDIAMsfdoZLWNJtdBR7A==", + "dev": true + }, + "@types/mime": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz", + "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==", + "dev": true + }, + "@types/mocha": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.0.3.tgz", + "integrity": "sha512-vyxR57nv8NfcU0GZu8EUXZLTbCMupIUwy95LJ6lllN+JRPG25CwMHoB1q5xKh8YKhQnHYRAn4yW2yuHbf/5xgg==", + "dev": true + }, + "@types/node": { + "version": "14.11.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.9.tgz", + "integrity": "sha512-iXuiZ65PL5c8VAlF426GVJGKcsnAb2rW2037LJe3G6eM6nz35bK9QAUOH3Ic3kF4ZcKLpM02sFkSzCflIpoIKA==", + "dev": true + }, + "@types/node-fetch": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.7.tgz", + "integrity": "sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw==", + "dev": true, + "requires": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, + "@types/qs": { + "version": "6.9.5", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.5.tgz", + "integrity": "sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", + "dev": true + }, + "@types/redis": { + "version": "2.8.28", + "resolved": "https://registry.npmjs.org/@types/redis/-/redis-2.8.28.tgz", + "integrity": "sha512-8l2gr2OQ969ypa7hFOeKqtFoY70XkHxISV0pAwmQ2nm6CSPb1brmTmqJCGGrekCo+pAZyWlNXr+Kvo6L/1wijA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/request": { + "version": "2.48.5", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.5.tgz", + "integrity": "sha512-/LO7xRVnL3DxJ1WkPGDQrp4VTV1reX9RkC85mJ+Qzykj2Bdw+mG15aAfDahc76HtknjzE16SX/Yddn6MxVbmGQ==", + "dev": true, + "requires": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + }, + "dependencies": { + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + } + } + }, + "@types/serve-static": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.5.tgz", + "integrity": "sha512-6M64P58N+OXjU432WoLLBQxbA0LRGBCRm7aAGQJ+SMC1IMl0dgRVi9EFfoDcS2a7Xogygk/eGN94CfwU9UF7UQ==", + "dev": true, + "requires": { + "@types/express-serve-static-core": "*", + "@types/mime": "*" + } + }, + "@types/tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A==", + "dev": true + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "requires": { + "event-target-shim": "^5.0.0" + } + }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -19,6 +189,40 @@ "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==" + } + } + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "ansi-align": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", @@ -34,16 +238,6 @@ "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", "dev": true }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, "anymatch": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", @@ -54,6 +248,12 @@ "picomatch": "^2.0.4" } }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -68,6 +268,11 @@ "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", @@ -82,24 +287,24 @@ } }, "assert-plus": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "aws-sign2": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", - "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz", + "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==" }, "babel-runtime": { "version": "6.26.0", @@ -134,14 +339,10 @@ } } }, - "base64url": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/base64url/-/base64url-1.0.6.tgz", - "integrity": "sha1-1k03XWinxkDZEuI1jRcNylu1RoE=", - "requires": { - "concat-stream": "~1.4.7", - "meow": "~2.0.0" - } + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" }, "bcrypt-pbkdf": { "version": "1.0.2", @@ -171,34 +372,6 @@ "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", "dev": true }, - "bl": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz", - "integrity": "sha1-/cqHGplxOqANGeO7ukHER4emU5g=", - "requires": { - "readable-stream": "~2.0.5" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "readable-stream": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", - "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "string_decoder": "~0.10.x", - "util-deprecate": "~1.0.1" - } - } - } - }, "body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", @@ -216,14 +389,6 @@ "type-is": "~1.6.17" } }, - "boom": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", - "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", - "requires": { - "hoek": "2.x.x" - } - }, "boxen": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", @@ -316,20 +481,6 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" }, - "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" - }, - "camelcase-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-1.0.0.tgz", - "integrity": "sha1-vRoRv5sxoc5JNJOpMN4aC69K1+w=", - "requires": { - "camelcase": "^1.0.1", - "map-obj": "^1.0.0" - } - }, "capture-stack-trace": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", @@ -337,21 +488,9 @@ "dev": true }, "caseless": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", - "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "chokidar": { "version": "3.3.0", @@ -448,27 +587,12 @@ "delayed-stream": "~1.0.0" } }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "concat-stream": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.4.11.tgz", - "integrity": "sha512-X3JMh8+4je3U1cQpG87+f9lXHDrqcb2MVLg9L7o8b1UZ0DzhRrUpdn65ttzu10PpJPPI3MQNkis+oha6TSA9Mw==", - "requires": { - "inherits": "~2.0.1", - "readable-stream": "~1.1.9", - "typedarray": "~0.0.5" - } - }, "configstore": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", @@ -543,14 +667,6 @@ "which": "^1.2.9" } }, - "cryptiles": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", - "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", - "requires": { - "boom": "2.x.x" - } - }, "crypto-random-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", @@ -563,13 +679,6 @@ "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "requires": { "assert-plus": "^1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } } }, "debug": { @@ -713,7 +822,8 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true }, "esprima": { "version": "4.0.1", @@ -726,6 +836,11 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, "execa": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", @@ -800,6 +915,21 @@ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "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==" + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -847,23 +977,14 @@ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.1.tgz", - "integrity": "sha1-rjFduaSQf6BlUCMEpm13M0de43w=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", + "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", + "dev": true, "requires": { - "async": "^2.0.1", - "combined-stream": "^1.0.5", - "mime-types": "^2.1.11" - }, - "dependencies": { - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "requires": { - "lodash": "^4.17.14" - } - } + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" } }, "forwarded": { @@ -903,29 +1024,32 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, - "gapitoken": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/gapitoken/-/gapitoken-0.1.5.tgz", - "integrity": "sha1-NXf8+1Qmvjp7jrrakmcSKdjMgc4=", + "gaxios": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.2.0.tgz", + "integrity": "sha512-+6WPeVzPvOshftpxJwRi2Ozez80tn/hdtOUag7+gajDHRJvAblKxTFSSMPtr2hmnLy7p0mvYz0rMXLBl8pSO7Q==", "requires": { - "jws": "~3.0.0", - "request": "^2.54.0" + "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==" + } } }, - "generate-function": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", - "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "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": { - "is-property": "^1.0.2" - } - }, - "generate-object-property": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", - "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", - "requires": { - "is-property": "^1.0.0" + "gaxios": "^3.0.0", + "json-bigint": "^1.0.0" } }, "get-caller-file": { @@ -939,11 +1063,6 @@ "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=" }, - "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" - }, "get-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", @@ -956,13 +1075,6 @@ "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "requires": { "assert-plus": "^1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } } }, "glob": { @@ -998,95 +1110,71 @@ } }, "google-auth-library": { - "version": "0.9.10", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-0.9.10.tgz", - "integrity": "sha1-SZPcB7tINLjKA1AhOmhzoyxgUbk=", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.1.tgz", + "integrity": "sha512-0WfExOx3FrLYnY88RICQxvpaNzdwjz44OsHqHkIoAJfjY6Jck6CZRl1ASWadk+wbJ0LhkQ8rNY4zZebKml4Ghg==", "requires": { - "async": "~1.4.2", - "gtoken": "^1.1.0", - "jws": "~3.0.0", - "lodash.noop": "~3.0.0", - "request": "~2.74.0", - "string-template": "~0.2.0" + "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": { - "async": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.4.2.tgz", - "integrity": "sha1-bJ7csRztTw3S8tQNsNSaEJwIiqs=" - }, - "node-uuid": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", - "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" - }, - "qs": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.2.3.tgz", - "integrity": "sha1-HPyyXBCpsrSDBT/zn138kjOQjP4=" - }, - "request": { - "version": "2.74.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.74.0.tgz", - "integrity": "sha1-dpPKdou7DqXIzgjAhKRe+gW4kqs=", + "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": { - "aws-sign2": "~0.6.0", - "aws4": "^1.2.1", - "bl": "~1.1.2", - "caseless": "~0.11.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.0", - "forever-agent": "~0.6.1", - "form-data": "~1.0.0-rc4", - "har-validator": "~2.0.6", - "hawk": "~3.1.3", - "http-signature": "~1.1.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.7", - "node-uuid": "~1.4.7", - "oauth-sign": "~0.8.1", - "qs": "~6.2.0", - "stringstream": "~0.0.4", - "tough-cookie": "~2.3.0", - "tunnel-agent": "~0.4.1" + "yallist": "^4.0.0" } }, - "string-template": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", - "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=" - }, - "tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", - "requires": { - "punycode": "^1.4.1" - } + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } }, "google-p12-pem": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-0.1.2.tgz", - "integrity": "sha1-M8RqsCGqc0+gMys5YKmj/8svMXc=", + "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.7.1" + "node-forge": "^0.10.0" } }, "googleapis": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-5.2.1.tgz", - "integrity": "sha1-YfwbEzakNzS7Bxjpo2W0+iKK4w8=", + "version": "54.1.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-54.1.0.tgz", + "integrity": "sha512-xkBk3wRRYeJxEp9bVyAaYQM7qt3Eo3wBoznk+XnXbcSAjhl+M7icsUGb6VGNbZJfvpKAU7/tfhsed50pfs/jmA==", "requires": { - "async": "~1.5.2", - "gapitoken": "~0.1.5", - "google-auth-library": "~0.9.7", - "request": "~2.72.0", - "string-template": "~1.0.0", - "url": "^0.11.0" + "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": { @@ -1121,25 +1209,35 @@ "dev": true }, "gtoken": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-1.2.3.tgz", - "integrity": "sha512-wQAJflfoqSgMWrSBk9Fg86q+sd6s7y6uJhIvvIPz++RElGlMtEqsdAR2oWwZ/WTEtp7P9xFbJRrT976oRgzJ/w==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.0.4.tgz", + "integrity": "sha512-U9wnSp4GZ7ov6zRdPuRHG4TuqEWqRRgT1gfXGNArhzBUn9byrPeH8uTmBWU/ZiWJJvTEmkjhDIC3mqHWdVi3xQ==", "requires": { - "google-p12-pem": "^0.1.0", - "jws": "^3.0.0", - "mime": "^1.4.1", - "request": "^2.72.0" + "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==" + } } }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, "har-validator": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", - "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", "requires": { - "chalk": "^1.1.1", - "commander": "^2.9.0", - "is-my-json-valid": "^2.12.4", - "pinkie-promise": "^2.0.0" + "ajv": "^6.12.3", + "har-schema": "^2.0.0" } }, "has": { @@ -1151,14 +1249,6 @@ "function-bind": "^1.1.1" } }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -1171,28 +1261,12 @@ "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", "dev": true }, - "hawk": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", - "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", - "requires": { - "boom": "2.x.x", - "cryptiles": "2.x.x", - "hoek": "2.x.x", - "sntp": "1.x.x" - } - }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, - "hoek": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" - }, "http": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/http/-/http-0.0.0.tgz", @@ -1211,15 +1285,39 @@ } }, "http-signature": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", - "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "requires": { - "assert-plus": "^0.2.0", + "assert-plus": "^1.0.0", "jsprim": "^1.2.2", "sshpk": "^1.7.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", @@ -1246,16 +1344,6 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, - "indent-string": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-1.2.2.tgz", - "integrity": "sha1-25m8xYPrarux5I3LsZmamGBBy2s=", - "requires": { - "get-stdin": "^4.0.1", - "minimist": "^1.1.0", - "repeating": "^1.1.0" - } - }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1329,11 +1417,6 @@ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, - "is-finite": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", - "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==" - }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", @@ -1359,23 +1442,6 @@ "is-path-inside": "^1.0.0" } }, - "is-my-ip-valid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", - "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==" - }, - "is-my-json-valid": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.20.0.tgz", - "integrity": "sha512-XTHBZSIIxNsIsZXg7XB5l8z/OBFosl1Wao4tXLpeC7eKU4Vm/kdop2azkPqULwnfGQjmeDIyey9g7afMMtdWAA==", - "requires": { - "generate-function": "^2.0.0", - "generate-object-property": "^1.1.0", - "is-my-ip-valid": "^1.0.0", - "jsonpointer": "^4.0.0", - "xtend": "^4.0.0" - } - }, "is-npm": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", @@ -1403,11 +1469,6 @@ "path-is-inside": "^1.0.1" } }, - "is-property": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" - }, "is-redirect": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", @@ -1449,11 +1510,6 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1485,21 +1541,29 @@ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, + "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" + } + }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, - "jsonpointer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", - "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=" - }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -1509,39 +1573,25 @@ "extsprintf": "1.3.0", "json-schema": "0.2.3", "verror": "1.10.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } } }, "jwa": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.0.2.tgz", - "integrity": "sha1-/Xlgnx53Limdzo3bdtAGWd2DUR8=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", "requires": { - "base64url": "~0.0.4", - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "^1.0.0" - }, - "dependencies": { - "base64url": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/base64url/-/base64url-0.0.6.tgz", - "integrity": "sha1-lZezazMNscQkdzIuqH6oAnSZuCs=" - } + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" } }, "jws": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.0.0.tgz", - "integrity": "sha1-2l8meJfdTpz4E3l52zP8VKPAVBg=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", "requires": { - "base64url": "~1.0.4", - "jwa": "~1.0.0" + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" } }, "latest-version": { @@ -1566,12 +1616,8 @@ "lodash": { "version": "4.17.19", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" - }, - "lodash.noop": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash.noop/-/lodash.noop-3.0.1.tgz", - "integrity": "sha1-OBiPTWUKOkdCWEObluxFsyYXEzw=" + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", + "dev": true }, "log-symbols": { "version": "3.0.0", @@ -1646,27 +1692,17 @@ "pify": "^3.0.0" } }, - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, - "meow": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-2.0.0.tgz", - "integrity": "sha1-j1MKjs9dQNP0tN+Tw0cpAPuiqPE=", - "requires": { - "camelcase-keys": "^1.0.0", - "indent-string": "^1.1.0", - "minimist": "^1.1.0", - "object-assign": "^1.0.0" - } - }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -1877,9 +1913,9 @@ "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" }, "node-forge": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.6.tgz", - "integrity": "sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw==" + "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", @@ -1950,14 +1986,9 @@ } }, "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" - }, - "object-assign": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-1.0.0.tgz", - "integrity": "sha1-5l3Idm07R7S4MHRlyDEdoDCwcKY=" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, "object-inspect": { "version": "1.7.0", @@ -2086,6 +2117,11 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, "picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", @@ -2098,30 +2134,12 @@ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "requires": { - "pinkie": "^2.0.0" - } - }, "prepend-http": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", "dev": true }, - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" - }, "promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", @@ -2152,6 +2170,11 @@ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", "dev": true }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, "pstree.remy": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.7.tgz", @@ -2159,20 +2182,15 @@ "dev": true }, "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" - }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -2201,17 +2219,6 @@ "strip-json-comments": "~2.0.1" } }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, "readdirp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", @@ -2274,51 +2281,47 @@ "rc": "^1.0.1" } }, - "repeating": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz", - "integrity": "sha1-PUEUIYh3U3SU+X93+Xhfq4EPpKw=", - "requires": { - "is-finite": "^1.0.0" - } - }, "request": { - "version": "2.72.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.72.0.tgz", - "integrity": "sha1-DOOheVEmILEEQfFMguIcEsDdtOE=", + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", "requires": { - "aws-sign2": "~0.6.0", - "aws4": "^1.2.1", - "bl": "~1.1.2", - "caseless": "~0.11.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.0", + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", "forever-agent": "~0.6.1", - "form-data": "~1.0.0-rc3", - "har-validator": "~2.0.6", - "hawk": "~3.1.3", - "http-signature": "~1.1.0", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.7", - "node-uuid": "~1.4.7", - "oauth-sign": "~0.8.1", - "qs": "~6.1.0", - "stringstream": "~0.0.4", - "tough-cookie": "~2.2.0", - "tunnel-agent": "~0.4.1" + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" }, "dependencies": { - "node-uuid": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", - "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } }, "qs": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.1.2.tgz", - "integrity": "sha1-tZ2JJdDJme9tY6z0rFq7CtqiS1Q=" + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" } } }, @@ -2429,12 +2432,20 @@ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, - "sntp": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", - "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, "requires": { - "hoek": "2.x.x" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, "sprintf-js": { @@ -2462,13 +2473,6 @@ "jsbn": "~0.1.0", "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } } }, "statuses": { @@ -2476,11 +2480,6 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, - "string-template": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/string-template/-/string-template-1.0.0.tgz", - "integrity": "sha1-np8iM9wA8hhxjsN5oopWc+zKi5Y=" - }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -2550,24 +2549,6 @@ "es-abstract": "^1.17.5" } }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - }, - "stringstream": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", - "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==" - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", @@ -2580,11 +2561,6 @@ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - }, "sync-mysql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/sync-mysql/-/sync-mysql-3.0.1.tgz", @@ -2717,14 +2693,42 @@ } }, "tough-cookie": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.2.tgz", - "integrity": "sha1-yDoYMPTl7wuT7yo0iOck+N4Basc=" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "ts-node": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz", + "integrity": "sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + } + } }, "tunnel-agent": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", - "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=" + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } }, "tweetnacl": { "version": "0.14.5", @@ -2745,6 +2749,12 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, + "typescript": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.3.tgz", + "integrity": "sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg==", + "dev": true + }, "undefsafe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", @@ -2823,19 +2833,18 @@ } } }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "uri-js": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", + "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" + "punycode": "^2.1.0" }, "dependencies": { "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" } } }, @@ -2848,6 +2857,11 @@ "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", @@ -2876,13 +2890,6 @@ "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } } }, "which": { @@ -2989,11 +2996,6 @@ "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", "dev": true }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" - }, "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", @@ -3080,12 +3082,18 @@ "yargs": "^13.3.0" } }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, "youtube-api": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/youtube-api/-/youtube-api-2.0.10.tgz", - "integrity": "sha512-2TxQYCO6mUmMLMRNvtwsmTROSRf0/oJFfw4y5c0LizIoTO8OmNvy7EkUmRoE9x1rY19r0KIhN6gheKTEt/oA5A==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/youtube-api/-/youtube-api-3.0.1.tgz", + "integrity": "sha512-r3N8xmE46desGsnTRD1KRiYnfPKElgp1BDMfeCkpyTMrE1qkTb8DxtJrQjEJxFpCE4s6xIQZa/cnWtTbOoNlkA==", "requires": { - "googleapis": "^5.2.1" + "googleapis": "^54.1.0" } } } diff --git a/package.json b/package.json index d3112d2..d102713 100644 --- a/package.json +++ b/package.json @@ -2,12 +2,12 @@ "name": "sponsor_block_server", "version": "0.1.0", "description": "Server that holds the SponsorBlock database", - "main": "index.js", + "main": "index.ts", "scripts": { - "test": "node test.js", + "test": "ts-node test.ts", "dev": "nodemon -x \"(npm test || echo test failed) && npm start\"", "dev:bash": "nodemon -x 'npm test ; npm start'", - "start": "node index.js" + "start": "ts-node index.ts" }, "author": "Ajay Ramachandran", "license": "MIT", @@ -19,12 +19,23 @@ "iso8601-duration": "^1.2.0", "node-fetch": "^2.6.0", "redis": "^3.0.2", + "request": "^2.88.2", "sync-mysql": "^3.0.1", "uuid": "^3.3.2", - "youtube-api": "^2.0.10" + "youtube-api": "^3.0.1" }, "devDependencies": { + "@types/better-sqlite3": "^5.4.0", + "@types/express": "^4.17.8", + "@types/express-rate-limit": "^5.1.0", + "@types/mocha": "^8.0.3", + "@types/node": "^14.11.9", + "@types/node-fetch": "^2.5.7", + "@types/redis": "^2.8.28", + "@types/request": "^2.48.5", "mocha": "^7.1.1", - "nodemon": "^2.0.2" + "nodemon": "^2.0.2", + "ts-node": "^9.0.0", + "typescript": "^4.0.3" } } diff --git a/src/app.js b/src/app.ts similarity index 55% rename from src/app.js rename to src/app.ts index bd8f4e1..177d405 100644 --- a/src/app.js +++ b/src/app.ts @@ -1,46 +1,38 @@ -var express = require('express'); +import express, {Request, RequestHandler, Response} from 'express'; +import {config} from './config'; +import {oldSubmitSponsorTimes} from './routes/oldSubmitSponsorTimes'; +import {oldGetVideoSponsorTimes} from './routes/oldGetVideoSponsorTimes'; +import {postSegmentShift} from './routes/postSegmentShift'; +import {postWarning} from './routes/postWarning'; +import {getIsUserVIP} from './routes/getIsUserVIP'; +import {deleteNoSegments} from './routes/deleteNoSegments'; +import {postNoSegments} from './routes/postNoSegments'; +import {getUserInfo} from './routes/getUserInfo'; +import {getDaysSavedFormatted} from './routes/getDaysSavedFormatted'; +import {getTotalStats} from './routes/getTotalStats'; +import {getTopUsers} from './routes/getTopUsers'; +import {getViewsForUser} from './routes/getViewsForUser'; +import {getSavedTimeForUser} from './routes/getSavedTimeForUser'; +import {addUserAsVIP} from './routes/addUserAsVIP'; +import {shadowBanUser} from './routes/shadowBanUser'; +import {getUsername} from './routes/getUsername'; +import {setUsername} from './routes/setUsername'; +import {viewedVideoSponsorTime} from './routes/viewedVideoSponsorTime'; +import {voteOnSponsorTime} from './routes/voteOnSponsorTime'; +import {getSkipSegmentsByHash} from './routes/getSkipSegmentsByHash'; +import {postSkipSegments} from './routes/postSkipSegments'; +import {endpoint as getSkipSegments} from './routes/getSkipSegments'; + +import {userCounter} from './middleware/userCounter'; +import {loggerMiddleware} from './middleware/logger'; +import {corsMiddleware} from './middleware/cors'; +import {rateLimitMiddleware} from './middleware/requestRateLimit'; + // Create a service (the app object is just a callback). -var app = express(); -var config = require('./config.js'); -var redis = require('./utils/redis.js'); -const getIP = require('./utils/getIP.js'); -const getHash = require('./utils/getHash.js'); - -// Middleware -const rateLimitMiddleware = require('./middleware/requestRateLimit.js'); -var corsMiddleware = require('./middleware/cors.js'); -var loggerMiddleware = require('./middleware/logger.js'); -const userCounter = require('./middleware/userCounter.js'); - -// Routes -var getSkipSegments = require('./routes/getSkipSegments.js').endpoint; -var postSkipSegments = require('./routes/postSkipSegments.js'); -var getSkipSegmentsByHash = require('./routes/getSkipSegmentsByHash.js'); -var voteOnSponsorTime = require('./routes/voteOnSponsorTime.js'); -var viewedVideoSponsorTime = require('./routes/viewedVideoSponsorTime.js'); -var setUsername = require('./routes/setUsername.js'); -var getUsername = require('./routes/getUsername.js'); -var shadowBanUser = require('./routes/shadowBanUser.js'); -var addUserAsVIP = require('./routes/addUserAsVIP.js'); -var getSavedTimeForUser = require('./routes/getSavedTimeForUser.js'); -var getViewsForUser = require('./routes/getViewsForUser.js'); -var getTopUsers = require('./routes/getTopUsers.js'); -var getTotalStats = require('./routes/getTotalStats.js'); -var getDaysSavedFormatted = require('./routes/getDaysSavedFormatted.js'); -var getUserInfo = require('./routes/getUserInfo.js'); -var postNoSegments = require('./routes/postNoSegments.js'); -var deleteNoSegments = require('./routes/deleteNoSegments.js'); -var getIsUserVIP = require('./routes/getIsUserVIP.js'); -var warnUser = require('./routes/postWarning.js'); -var postSegmentShift = require('./routes/postSegmentShift.js'); - -// Old Routes -var oldGetVideoSponsorTimes = require('./routes/oldGetVideoSponsorTimes.js'); -var oldSubmitSponsorTimes = require('./routes/oldSubmitSponsorTimes.js'); - +const app = express(); // Rate limit endpoint lists -let voteEndpoints = [voteOnSponsorTime.endpoint]; -let viewEndpoints = [viewedVideoSponsorTime]; +const voteEndpoints: RequestHandler[] = [voteOnSponsorTime]; +const viewEndpoints: RequestHandler[] = [viewedVideoSponsorTime]; if (config.rateLimit) { if (config.rateLimit.vote) voteEndpoints.unshift(rateLimitMiddleware(config.rateLimit.vote)); if (config.rateLimit.view) viewEndpoints.unshift(rateLimitMiddleware(config.rateLimit.view)); @@ -49,7 +41,7 @@ if (config.rateLimit) { //setup CORS correctly app.use(corsMiddleware); app.use(loggerMiddleware); -app.use(express.json()) +app.use(express.json()); if (config.userCounterURL) app.use(userCounter); @@ -122,16 +114,16 @@ app.delete('/api/noSegments', deleteNoSegments); app.get('/api/isUserVIP', getIsUserVIP); //sent user a warning -app.post('/api/warnUser', warnUser); +app.post('/api/warnUser', postWarning); //get if user is a vip app.post('/api/segmentShift', postSegmentShift); -app.get('/database.db', function (req, res) { - res.sendFile("./databases/sponsorTimes.db", { root: "./" }); +app.get('/database.db', function (req: Request, res: Response) { + res.sendFile("./databases/sponsorTimes.db", {root: "./"}); }); // Create an HTTP service. -module.exports = function createServer (callback) { +export function createServer(callback: () => void) { return app.listen(config.port, callback); } diff --git a/src/config.js b/src/config.js deleted file mode 100644 index b3f30d3..0000000 --- a/src/config.js +++ /dev/null @@ -1,37 +0,0 @@ - -const fs = require('fs'); -let config = {}; - -// Check to see if launched in test mode -if (process.env.npm_lifecycle_script === 'node test.js') { - config = JSON.parse(fs.readFileSync('test.json')); -} else { - config = JSON.parse(fs.readFileSync('config.json')); -} - -addDefaults(config, { - "port": 80, - "behindProxy": "X-Forwarded-For", - "db": "./databases/sponsorTimes.db", - "privateDB": "./databases/private.db", - "createDatabaseIfNotExist": true, - "schemaFolder": "./databases", - "dbSchema": "./databases/_sponsorTimes.db.sql", - "privateDBSchema": "./databases/_private.db.sql", - "readOnly": false, - "webhooks": [], - "categoryList": ["sponsor", "intro", "outro", "interaction", "selfpromo", "music_offtopic"], - "maxNumberOfActiveWarnings": 3, - "hoursAfterWarningExpires": 24 -}) - -module.exports = config; - -// Add defaults -function addDefaults(config, defaults) { - for (const key in defaults) { - if(!config.hasOwnProperty(key)) { - config[key] = defaults[key]; - } - } -}; \ No newline at end of file diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..beeb6ed --- /dev/null +++ b/src/config.ts @@ -0,0 +1,58 @@ +import fs from 'fs'; +import {SBSConfig} from "./types/config.model"; + +const isTestMode = process.env.npm_lifecycle_script === 'ts-node test.ts'; +const configFile = isTestMode ? 'test.json' : 'config.json'; +export const config: SBSConfig = JSON.parse(fs.readFileSync(configFile).toString('utf8')); + +addDefaults(config, { + port: 80, + behindProxy: "X-Forwarded-For", + db: "./databases/sponsorTimes.db", + privateDB: "./databases/private.db", + createDatabaseIfNotExist: true, + schemaFolder: "./databases", + dbSchema: "./databases/_sponsorTimes.db.sql", + privateDBSchema: "./databases/_private.db.sql", + readOnly: false, + webhooks: [], + categoryList: ["sponsor", "intro", "outro", "interaction", "selfpromo", "music_offtopic"], + maxNumberOfActiveWarnings: 3, + hoursAfterWarningExpires: 24, + adminUserID: "", + discordCompletelyIncorrectReportWebhookURL: "", + discordFirstTimeSubmissionsWebhookURL: "", + discordNeuralBlockRejectWebhookURL: "", + discordReportChannelWebhookURL: "", + getTopUsersCacheTimeMinutes: 0, + globalSalt: null, + mode: "", + neuralBlockURL: null, + proxySubmission: null, + rateLimit: { + vote: { + windowMs: 900000, + max: 20, + message: "Too many votes, please try again later", + statusCode: 429, + }, + view: { + windowMs: 900000, + max: 20, + statusCode: 200, + message: "", // TODO TYPESCRIPT whats the msg? + }, + }, + userCounterURL: null, + youtubeAPIKey: null, +}); + +// Add defaults +function addDefaults(config: SBSConfig, defaults: SBSConfig) { + for (const key in defaults) { + if (!config.hasOwnProperty(key)) { + // @ts-ignore + config[key] = defaults[key]; + } + } +} diff --git a/src/databases/IDatabase.ts b/src/databases/IDatabase.ts new file mode 100644 index 0000000..f30a532 --- /dev/null +++ b/src/databases/IDatabase.ts @@ -0,0 +1,13 @@ +export interface IDatabase { + init(): void; + + prepare(type: QueryType, query: string, params: any[]): any; + + get(query: string, params: any[]): TModel; + getAll(query: string, params: any[]): TModel[]; + run(query: string, params: any[]): void; + + exec(query: string): any; +} + +export type QueryType = 'get' | 'all' | 'run'; diff --git a/src/databases/Mysql.js b/src/databases/Mysql.js deleted file mode 100644 index a6b2850..0000000 --- a/src/databases/Mysql.js +++ /dev/null @@ -1,28 +0,0 @@ -var MysqlInterface = require('sync-mysql'); -const logger = require('../utils/logger.js'); - -class Mysql { - constructor(msConfig) { - this.connection = new MysqlInterface(msConfig); - } - - exec(query) { - this.prepare('run', query, []); - } - - prepare (type, query, params) { - logger.debug("prepare (mysql): type: " + type + ", query: " + query + ", params: " + params); - if (type === 'get') { - return this.connection.query(query, params)[0]; - } else if (type === 'run') { - this.connection.query(query, params); - } else if (type === 'all') { - return this.connection.query(query, params); - } else { - logger.warn('returning undefined...'); - return undefined; - } - } -} - -module.exports = Mysql; \ No newline at end of file diff --git a/src/databases/Mysql.ts b/src/databases/Mysql.ts new file mode 100644 index 0000000..5001a4c --- /dev/null +++ b/src/databases/Mysql.ts @@ -0,0 +1,49 @@ +import {Logger} from '../utils/logger'; +import {IDatabase, QueryType} from './IDatabase'; +// @ts-ignore +import MysqlInterface from 'sync-mysql'; + +export class Mysql implements IDatabase { + private connection: any; + + constructor(private config: any) { + } + + init() { + this.connection = new MysqlInterface(this.config); + } + + exec(query: string) { + this.prepare('run', query, []); + } + + prepare(type: QueryType, query: string, params: any[]) { + Logger.debug(`prepare (mysql): type: ${type}, query: ${query}, params: ${params}`); + const queryResult = this.connection.query(query, params); + + switch (type) { + case 'get': { + return queryResult[0]; + } + case 'all': { + return queryResult; + } + case 'run': { + break; + } + } + } + + public get(query: string, params: any[]): TModel { + return this.prepare('get', query, params); + } + + public getAll(query: string, params: any[]): TModel[] { + return this.prepare('all', query, params); + } + + public run(query: string, params: any[]): void { + this.prepare('run', query, params); + } +} + diff --git a/src/databases/Sqlite.js b/src/databases/Sqlite.js deleted file mode 100644 index 6b71fec..0000000 --- a/src/databases/Sqlite.js +++ /dev/null @@ -1,33 +0,0 @@ -const { db } = require("./databases"); -var config = require('../config.js'); -const logger = require('../utils/logger.js'); - -class Sqlite { - constructor(connection) { - this.connection = connection; - } - - getConnection() { - return this.connection; - } - - prepare(type, query, params) { - if (type === 'get') { - return this.connection.prepare(query).get(...params); - } else if (type === 'run') { - this.connection.prepare(query).run(...params); - } else if (type === 'all') { - return this.connection.prepare(query).all(...params); - } else { - logger.debug('sqlite query: returning undefined') - logger.debug("prepare: type: " + type + ", query: " + query + ", params: " + params); - return undefined; - } - } - - exec(query) { - return this.connection.exec(query); - } -} - -module.exports = Sqlite; \ No newline at end of file diff --git a/src/databases/Sqlite.ts b/src/databases/Sqlite.ts new file mode 100644 index 0000000..9a3b1fd --- /dev/null +++ b/src/databases/Sqlite.ts @@ -0,0 +1,107 @@ +import {IDatabase, QueryType} from './IDatabase'; +import Sqlite3, {Database, Database as SQLiteDatabase} from 'better-sqlite3'; +import fs from "fs"; +import path from "path"; +import {getHash} from '../utils/getHash'; +import {Logger} from '../utils/logger'; + +export class Sqlite implements IDatabase { + private db: SQLiteDatabase; + + constructor(private config: SqliteConfig) + { + } + + prepare(type: QueryType, query: string, params: any[]) { + const preparedQuery = this.db.prepare(query); + + switch (type) { + case 'get': { + return preparedQuery.get(...params); + } + case 'all': { + return preparedQuery.all(...params); + } + case 'run': { + preparedQuery.run(...params); + break; + } + } + } + + get(query: string, params: any[]): TModel { + return this.prepare('get', query, params); + } + getAll(query: string, params: any[]): TModel[] { + return this.prepare('all', query, params); + } + run(query: string, params: any[]): void { + this.prepare('run', query, params); + } + + exec(query: string) { + return this.db.exec(query); + } + + init() { + // Make dirs if required + if (!fs.existsSync(path.join(this.config.dbPath, "../"))) { + fs.mkdirSync(path.join(this.config.dbPath, "../")); + } + + this.db = new Sqlite3(this.config.dbPath, {readonly: this.config.readOnly, fileMustExist: !this.config.createDbIfNotExists}); + + if (this.config.createDbIfNotExists && !this.config.readOnly && fs.existsSync(this.config.dbSchemaFileName)) { + this.db.exec(fs.readFileSync(this.config.dbSchemaFileName).toString()); + } + + if (!this.config.readOnly) { + this.db.function("sha256", (str: string) => { + return getHash(str, 1); + }); + + // Upgrade database if required + Sqlite.upgradeDB(this.db, this.config.fileNamePrefix, this.config.dbSchemaFolder); + } + + // Enable WAL mode checkpoint number + if (this.config.enableWalCheckpointNumber) { + this.db.exec("PRAGMA journal_mode=WAL;"); + this.db.exec("PRAGMA wal_autocheckpoint=1;"); + } + + // Enable Memory-Mapped IO + this.db.exec("pragma mmap_size= 500000000;"); + } + + attachDatabase(database: string, attachAs: string) { + this.db.prepare(`ATTACH ? as ${attachAs}`).run(database); + } + + private static upgradeDB(db: Database, fileNamePrefix: string, schemaFolder: string) { + const versionCodeInfo = db.prepare("SELECT value FROM config WHERE key = ?").get("version"); + let versionCode = versionCodeInfo ? versionCodeInfo.value : 0; + + let path = schemaFolder + "/_upgrade_" + fileNamePrefix + "_" + (parseInt(versionCode) + 1) + ".sql"; + Logger.debug('db update: trying ' + path); + while (fs.existsSync(path)) { + Logger.debug('db update: updating ' + path); + db.exec(fs.readFileSync(path).toString()); + + versionCode = db.prepare("SELECT value FROM config WHERE key = ?").get("version").value; + path = schemaFolder + "/_upgrade_" + fileNamePrefix + "_" + (parseInt(versionCode) + 1) + ".sql"; + Logger.debug('db update: trying ' + path); + } + Logger.debug('db update: no file ' + path); + } +} + +export interface SqliteConfig { + dbPath: string; + dbSchemaFileName: string; + dbSchemaFolder: string; + fileNamePrefix: string; + readOnly: boolean; + createDbIfNotExists: boolean; + enableWalCheckpointNumber: boolean +} diff --git a/src/databases/databases.js b/src/databases/databases.js deleted file mode 100644 index 98fd8d7..0000000 --- a/src/databases/databases.js +++ /dev/null @@ -1,81 +0,0 @@ -var config = require('../config.js'); -var Sqlite3 = require('better-sqlite3'); -var fs = require('fs'); -var path = require('path'); -var Sqlite = require('./Sqlite.js') -var Mysql = require('./Mysql.js'); -const logger = require('../utils/logger.js'); -const getHash = require('../utils/getHash.js'); - -let options = { - readonly: config.readOnly, - fileMustExist: !config.createDatabaseIfNotExist -}; - -if (config.mysql) { - module.exports = { - db: new Mysql(config.mysql), - privateDB: new Mysql(config.privateMysql) - }; -} else { - // Make dirs if required - if (!fs.existsSync(path.join(config.db, "../"))) { - fs.mkdirSync(path.join(config.db, "../")); - } - if (!fs.existsSync(path.join(config.privateDB, "../"))) { - fs.mkdirSync(path.join(config.privateDB, "../")); - } - - var db = new Sqlite3(config.db, options); - var privateDB = new Sqlite3(config.privateDB, options); - - if (config.createDatabaseIfNotExist && !config.readOnly) { - if (fs.existsSync(config.dbSchema)) db.exec(fs.readFileSync(config.dbSchema).toString()); - if (fs.existsSync(config.privateDBSchema)) privateDB.exec(fs.readFileSync(config.privateDBSchema).toString()); - } - - if (!config.readOnly) { - db.function("sha256", (string) => { - return getHash(string, 1); - }); - - // Upgrade database if required - ugradeDB(db, "sponsorTimes"); - ugradeDB(privateDB, "private") - } - - // Attach private db to main db - db.prepare("ATTACH ? as privateDB").run(config.privateDB); - - // Enable WAL mode checkpoint number - if (!config.readOnly && config.mode === "production") { - db.exec("PRAGMA journal_mode=WAL;"); - db.exec("PRAGMA wal_autocheckpoint=1;"); - } - - // Enable Memory-Mapped IO - db.exec("pragma mmap_size= 500000000;"); - privateDB.exec("pragma mmap_size= 500000000;"); - - module.exports = { - db: new Sqlite(db), - privateDB: new Sqlite(privateDB) - }; - - function ugradeDB(db, prefix) { - let versionCodeInfo = db.prepare("SELECT value FROM config WHERE key = ?").get("version"); - let versionCode = versionCodeInfo ? versionCodeInfo.value : 0; - - let path = config.schemaFolder + "/_upgrade_" + prefix + "_" + (parseInt(versionCode) + 1) + ".sql"; - logger.debug('db update: trying ' + path); - while (fs.existsSync(path)) { - logger.debug('db update: updating ' + path); - db.exec(fs.readFileSync(path).toString()); - - versionCode = db.prepare("SELECT value FROM config WHERE key = ?").get("version").value; - path = config.schemaFolder + "/_upgrade_" + prefix + "_" + (parseInt(versionCode) + 1) + ".sql"; - logger.debug('db update: trying ' + path); - } - logger.debug('db update: no file ' + path); - } -} \ No newline at end of file diff --git a/src/databases/databases.ts b/src/databases/databases.ts new file mode 100644 index 0000000..3b9e719 --- /dev/null +++ b/src/databases/databases.ts @@ -0,0 +1,47 @@ +import {config} from '../config'; +import {Sqlite} from './Sqlite'; +import {Mysql} from './Mysql'; +import {IDatabase} from './IDatabase'; + + +let db: IDatabase; +let privateDB: IDatabase; +if (config.mysql) { + db = new Mysql(config.mysql); + privateDB = new Mysql(config.privateMysql); +} +else { + db = new Sqlite({ + dbPath: config.db, + dbSchemaFileName: config.dbSchema, + dbSchemaFolder: config.schemaFolder, + fileNamePrefix: 'sponsorTimes', + readOnly: config.readOnly, + createDbIfNotExists: config.createDatabaseIfNotExist, + enableWalCheckpointNumber: !config.readOnly && config.mode === "production" + }); + privateDB = new Sqlite({ + dbPath: config.privateDB, + dbSchemaFileName: config.privateDBSchema, + dbSchemaFolder: config.schemaFolder, + fileNamePrefix: 'private', + readOnly: config.readOnly, + createDbIfNotExists: config.createDatabaseIfNotExist, + enableWalCheckpointNumber: false + }); +} +function initDb() { + db.init(); + privateDB.init(); + + if (db instanceof Sqlite) { + // Attach private db to main db + (db as Sqlite).attachDatabase(config.privateDB, 'privateDB'); + } +} + +export { + db, + privateDB, + initDb, +} diff --git a/src/middleware/cors.js b/src/middleware/cors.js deleted file mode 100644 index 490d230..0000000 --- a/src/middleware/cors.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = function corsMiddleware(req, res, next) { - res.header("Access-Control-Allow-Origin", "*"); - res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); - next(); -} \ No newline at end of file diff --git a/src/middleware/cors.ts b/src/middleware/cors.ts new file mode 100644 index 0000000..2b25d9b --- /dev/null +++ b/src/middleware/cors.ts @@ -0,0 +1,7 @@ +import {NextFunction, Request, Response} from 'express'; + +export function corsMiddleware(req: Request, res: Response, next: NextFunction) { + res.header("Access-Control-Allow-Origin", "*"); + res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); + next(); +} diff --git a/src/middleware/logger.js b/src/middleware/logger.js deleted file mode 100644 index 43a0c36..0000000 --- a/src/middleware/logger.js +++ /dev/null @@ -1,6 +0,0 @@ -const log = require('../utils/logger.js'); // log not logger to not interfere with function name - -module.exports = function logger (req, res, next) { - log.info("Request recieved: " + req.method + " " + req.url); - next(); -} \ No newline at end of file diff --git a/src/middleware/logger.ts b/src/middleware/logger.ts new file mode 100644 index 0000000..942e78e --- /dev/null +++ b/src/middleware/logger.ts @@ -0,0 +1,7 @@ +import {Logger} from '../utils/logger'; +import {NextFunction, Request, Response} from 'express'; + +export function loggerMiddleware(req: Request, res: Response, next: NextFunction) { + Logger.info(`Request received: ${req.method} ${req.url}`); + next(); +} diff --git a/src/middleware/requestRateLimit.js b/src/middleware/requestRateLimit.js deleted file mode 100644 index b66a0aa..0000000 --- a/src/middleware/requestRateLimit.js +++ /dev/null @@ -1,18 +0,0 @@ -const getIP = require('../utils/getIP.js'); -const getHash = require('../utils/getHash.js'); -const rateLimit = require('express-rate-limit'); - -module.exports = (limitConfig) => rateLimit({ - windowMs: limitConfig.windowMs, - max: limitConfig.max, - message: limitConfig.message, - statusCode: limitConfig.statusCode, - headers: false, - keyGenerator: (req /*, res*/) => { - return getHash(getIP(req), 1); - }, - skip: (/*req, res*/) => { - // skip rate limit if running in test mode - return process.env.npm_lifecycle_script === 'node test.js'; - } -}); diff --git a/src/middleware/requestRateLimit.ts b/src/middleware/requestRateLimit.ts new file mode 100644 index 0000000..e3756be --- /dev/null +++ b/src/middleware/requestRateLimit.ts @@ -0,0 +1,21 @@ +import {getIP} from '../utils/getIP'; +import {getHash} from '../utils/getHash'; +import rateLimit from 'express-rate-limit'; +import {RateLimitConfig} from '../types/config.model'; + +export function rateLimitMiddleware(limitConfig: RateLimitConfig): rateLimit.RateLimit { + return rateLimit({ + windowMs: limitConfig.windowMs, + max: limitConfig.max, + message: limitConfig.message, + statusCode: limitConfig.statusCode, + headers: false, + keyGenerator: (req) => { + return getHash(getIP(req), 1); + }, + skip: (/*req, res*/) => { + // skip rate limit if running in test mode + return process.env.npm_lifecycle_script === 'ts-node test.ts'; + }, + }); +} diff --git a/src/middleware/userCounter.js b/src/middleware/userCounter.js deleted file mode 100644 index defcd53..0000000 --- a/src/middleware/userCounter.js +++ /dev/null @@ -1,13 +0,0 @@ -const fetch = require('node-fetch'); - -const config = require('../config.js'); -const getIP = require('../utils/getIP.js'); -const getHash = require('../utils/getHash.js'); -const logger = require('../utils/logger.js'); - -module.exports = function userCounter(req, res, next) { - fetch(config.userCounterURL + "/api/v1/addIP?hashedIP=" + getHash(getIP(req), 1), { method: "POST" }) - .catch(() => logger.debug("Failing to connect to user counter at: " + config.userCounterURL)) - - next(); -} \ No newline at end of file diff --git a/src/middleware/userCounter.ts b/src/middleware/userCounter.ts new file mode 100644 index 0000000..e6a2c71 --- /dev/null +++ b/src/middleware/userCounter.ts @@ -0,0 +1,13 @@ +import fetch from 'node-fetch'; +import {Logger} from '../utils/logger'; +import {config} from '../config'; +import {getIP} from '../utils/getIP'; +import {getHash} from '../utils/getHash'; +import {NextFunction, Request, Response} from 'express'; + +export function userCounter(req: Request, res: Response, next: NextFunction) { + fetch(config.userCounterURL + "/api/v1/addIP?hashedIP=" + getHash(getIP(req), 1), {method: "POST"}) + .catch(() => Logger.debug("Failing to connect to user counter at: " + config.userCounterURL)); + + next(); +} diff --git a/src/routes/addUserAsVIP.js b/src/routes/addUserAsVIP.ts similarity index 57% rename from src/routes/addUserAsVIP.js rename to src/routes/addUserAsVIP.ts index 03d8502..a30493f 100644 --- a/src/routes/addUserAsVIP.js +++ b/src/routes/addUserAsVIP.ts @@ -1,19 +1,15 @@ -var fs = require('fs'); -var config = require('../config.js'); +import {getHash} from '../utils/getHash'; +import {db} from '../databases/databases'; +import {config} from '../config'; +import {Request, Response} from 'express'; -var db = require('../databases/databases.js').db; -var getHash = require('../utils/getHash.js'); +export async function addUserAsVIP(req: Request, res: Response) { + const userID = req.query.userID as string; + let adminUserIDInput = req.query.adminUserID as string; -module.exports = async function addUserAsVIP (req, res) { - let userID = req.query.userID; - let adminUserIDInput = req.query.adminUserID; - - let enabled = req.query.enabled; - if (enabled === undefined){ - enabled = true; - } else { - enabled = enabled === "true"; - } + const enabled = req.query.enabled === undefined + ? false + : req.query.enabled === 'true'; if (userID == undefined || adminUserIDInput == undefined) { //invalid request @@ -31,7 +27,7 @@ module.exports = async function addUserAsVIP (req, res) { } //check to see if this user is already a vip - let row = db.prepare('get', "SELECT count(*) as userCount FROM vipUsers WHERE userID = ?", [userID]); + const row = db.prepare('get', "SELECT count(*) as userCount FROM vipUsers WHERE userID = ?", [userID]); if (enabled && row.userCount == 0) { //add them to the vip list @@ -42,4 +38,4 @@ module.exports = async function addUserAsVIP (req, res) { } res.sendStatus(200); -} \ No newline at end of file +} diff --git a/src/routes/deleteNoSegments.js b/src/routes/deleteNoSegments.ts similarity index 52% rename from src/routes/deleteNoSegments.js rename to src/routes/deleteNoSegments.ts index adb44ba..5fa694e 100644 --- a/src/routes/deleteNoSegments.js +++ b/src/routes/deleteNoSegments.ts @@ -1,43 +1,43 @@ -const db = require('../databases/databases.js').db; -const getHash = require('../utils/getHash.js'); -const isUserVIP = require('../utils/isUserVIP.js'); -const logger = require('../utils/logger.js'); +import {Request, Response} from 'express'; +import {isUserVIP} from '../utils/isUserVIP'; +import {getHash} from '../utils/getHash'; +import {db} from '../databases/databases'; -module.exports = (req, res) => { +export function deleteNoSegments(req: Request, res: Response) { // Collect user input data - let videoID = req.body.videoID; + const videoID = req.body.videoID; let userID = req.body.userID; - let categories = req.body.categories; + const categories = req.body.categories; // Check input data is valid - if (!videoID - || !userID - || !categories - || !Array.isArray(categories) + if (!videoID + || !userID + || !categories + || !Array.isArray(categories) || categories.length === 0 ) { res.status(400).json({ - message: 'Bad Format' + message: 'Bad Format', }); return; } // Check if user is VIP userID = getHash(userID); - let userIsVIP = isUserVIP(userID); + const userIsVIP = isUserVIP(userID); if (!userIsVIP) { res.status(403).json({ - message: 'Must be a VIP to mark videos.' + message: 'Must be a VIP to mark videos.', }); return; } - db.prepare("all", 'SELECT * FROM noSegments WHERE videoID = ?', [videoID]).filter((entry) => { + db.prepare("all", 'SELECT * FROM noSegments WHERE videoID = ?', [videoID]).filter((entry: any) => { return (categories.indexOf(entry.category) !== -1); - }).forEach((entry) => { + }).forEach((entry: any) => { db.prepare('run', 'DELETE FROM noSegments WHERE videoID = ? AND category = ?', [videoID, entry.category]); }); res.status(200).json({message: 'Removed no segments entrys for video ' + videoID}); -}; \ No newline at end of file +} diff --git a/src/routes/getDaysSavedFormatted.js b/src/routes/getDaysSavedFormatted.js deleted file mode 100644 index 94793b7..0000000 --- a/src/routes/getDaysSavedFormatted.js +++ /dev/null @@ -1,12 +0,0 @@ -var db = require('../databases/databases.js').db; - -module.exports = function getDaysSavedFormatted (req, res) { - let row = db.prepare('get', "SELECT SUM((endTime - startTime) / 60 / 60 / 24 * views) as daysSaved from sponsorTimes where shadowHidden != 1", []); - - if (row !== undefined) { - //send this result - res.send({ - daysSaved: row.daysSaved.toFixed(2) - }); - } -} diff --git a/src/routes/getDaysSavedFormatted.ts b/src/routes/getDaysSavedFormatted.ts new file mode 100644 index 0000000..0179425 --- /dev/null +++ b/src/routes/getDaysSavedFormatted.ts @@ -0,0 +1,13 @@ +import {db} from '../databases/databases'; +import {Request, Response} from 'express'; + +export function getDaysSavedFormatted(req: Request, res: Response) { + let row = db.prepare('get', "SELECT SUM((endTime - startTime) / 60 / 60 / 24 * views) as daysSaved from sponsorTimes where shadowHidden != 1", []); + + if (row !== undefined) { + //send this result + res.send({ + daysSaved: row.daysSaved.toFixed(2), + }); + } +} diff --git a/src/routes/getIsUserVIP.js b/src/routes/getIsUserVIP.ts similarity index 52% rename from src/routes/getIsUserVIP.js rename to src/routes/getIsUserVIP.ts index 63efd02..3cc23f0 100644 --- a/src/routes/getIsUserVIP.js +++ b/src/routes/getIsUserVIP.ts @@ -1,11 +1,10 @@ -var db = require('../databases/databases.js').db; +import {Logger} from '../utils/logger'; +import {getHash} from '../utils/getHash'; +import {isUserVIP} from '../utils/isUserVIP'; +import {Request, Response} from 'express'; -var getHash = require('../utils/getHash.js'); -const logger = require('../utils/logger.js'); -const isUserVIP = require('../utils/isUserVIP.js'); - -module.exports = (req, res) => { - let userID = req.query.userID; +export function getIsUserVIP(req: Request, res: Response): void { + let userID = req.query.userID as string; if (userID == undefined) { //invalid request @@ -20,10 +19,10 @@ module.exports = (req, res) => { let vipState = isUserVIP(userID); res.status(200).json({ hashedUserID: userID, - vip: vipState + vip: vipState, }); } catch (err) { - logger.error(err); + Logger.error(err); res.sendStatus(500); return; diff --git a/src/routes/getSavedTimeForUser.js b/src/routes/getSavedTimeForUser.js deleted file mode 100644 index fd04d28..0000000 --- a/src/routes/getSavedTimeForUser.js +++ /dev/null @@ -1,32 +0,0 @@ -var db = require('../databases/databases.js').db; -var getHash = require('../utils/getHash.js'); - -module.exports = function getSavedTimeForUser (req, res) { - let userID = req.query.userID; - - if (userID == undefined) { - //invalid request - res.sendStatus(400); - return; - } - - //hash the userID - userID = getHash(userID); - - try { - let row = db.prepare("get", "SELECT SUM((endTime - startTime) / 60 * views) as minutesSaved FROM sponsorTimes WHERE userID = ? AND votes > -1 AND shadowHidden != 1 ", [userID]); - - if (row.minutesSaved != null) { - res.send({ - timeSaved: row.minutesSaved - }); - } else { - res.sendStatus(404); - } - } catch (err) { - console.log(err); - res.sendStatus(500); - - return; - } -} \ No newline at end of file diff --git a/src/routes/getSavedTimeForUser.ts b/src/routes/getSavedTimeForUser.ts new file mode 100644 index 0000000..fbf28dc --- /dev/null +++ b/src/routes/getSavedTimeForUser.ts @@ -0,0 +1,33 @@ +import {db} from '../databases/databases'; +import {Request, Response} from 'express'; +import {getHash} from '../utils/getHash'; + +export function getSavedTimeForUser(req: Request, res: Response) { + let userID = req.query.userID as string; + + if (userID == undefined) { + //invalid request + res.sendStatus(400); + return; + } + + //hash the userID + userID = getHash(userID); + + try { + let row = db.prepare("get", "SELECT SUM((endTime - startTime) / 60 * views) as minutesSaved FROM sponsorTimes WHERE userID = ? AND votes > -1 AND shadowHidden != 1 ", [userID]); + + if (row.minutesSaved != null) { + res.send({ + timeSaved: row.minutesSaved, + }); + } else { + res.sendStatus(404); + } + } catch (err) { + console.log(err); + res.sendStatus(500); + + return; + } +} diff --git a/src/routes/getSkipSegments.js b/src/routes/getSkipSegments.ts similarity index 77% rename from src/routes/getSkipSegments.js rename to src/routes/getSkipSegments.ts index 81ea763..34c668c 100644 --- a/src/routes/getSkipSegments.js +++ b/src/routes/getSkipSegments.ts @@ -1,27 +1,26 @@ -var config = require('../config.js'); +import {config} from '../config'; +import {db, privateDB} from '../databases/databases'; +import {Logger} from '../utils/logger'; +import {getHash} from '../utils/getHash'; +import {getIP} from '../utils/getIP'; +import {Request, Response} from 'express'; -var databases = require('../databases/databases.js'); -var db = databases.db; -var privateDB = databases.privateDB; -var logger = require('../utils/logger.js'); -var getHash = require('../utils/getHash.js'); -var getIP = require('../utils/getIP.js'); +function cleanGetSegments(req: Request, videoID: string, categories: any[]) { + let userHashedIP: any; + let shadowHiddenSegments: any[]; -function cleanGetSegments(req, videoID, categories) { - let userHashedIP, shadowHiddenSegments; - - let segments = []; + let segments: { category: any; segment: any[]; UUID: any; }[] = []; try { for (const category of categories) { - const categorySegments = db + const categorySegments: any[] = db .prepare( 'all', 'SELECT startTime, endTime, votes, UUID, shadowHidden FROM sponsorTimes WHERE videoID = ? and category = ? ORDER BY startTime', - [videoID, category] + [videoID, category], ) - .filter(segment => { + .filter((segment: any) => { if (segment.votes < -1) { return false; //too untrustworthy, just ignore it } @@ -46,7 +45,7 @@ function cleanGetSegments(req, videoID, categories) { }); }); - chooseSegments(categorySegments).forEach(chosenSegment => { + chooseSegments(categorySegments).forEach((chosenSegment: any) => { segments.push({ category, segment: [chosenSegment.startTime, chosenSegment.endTime], @@ -58,7 +57,7 @@ function cleanGetSegments(req, videoID, categories) { return segments; } catch (err) { if (err) { - logger.error(err); + Logger.error(err); return null; } } @@ -67,7 +66,7 @@ function cleanGetSegments(req, videoID, categories) { //gets a weighted random choice from the choices array based on their `votes` property. //amountOfChoices specifies the maximum amount of choices to return, 1 or more. //choices are unique -function getWeightedRandomChoice(choices, amountOfChoices) { +function getWeightedRandomChoice(choices: any[], amountOfChoices: number) { //trivial case: no need to go through the whole process if (amountOfChoices >= choices.length) { return choices; @@ -79,10 +78,10 @@ function getWeightedRandomChoice(choices, amountOfChoices) { //The 3 makes -2 the minimum votes before being ignored completely //https://www.desmos.com/calculator/c1duhfrmts //this can be changed if this system increases in popularity. - const weight = Math.exp((choice.votes + 3), 0.85); + const weight = Math.exp((choice.votes + 3)); // TODO TYPESCRIPT what was this 0.85? totalWeight += weight; - return { ...choice, weight }; + return {...choice, weight}; }); //iterate and find amountOfChoices choices @@ -109,18 +108,18 @@ function getWeightedRandomChoice(choices, amountOfChoices) { //Only one similar time will be returned, randomly generated based on the sqrt of votes. //This allows new less voted items to still sometimes appear to give them a chance at getting votes. //Segments with less than -1 votes are already ignored before this function is called -function chooseSegments(segments) { +function chooseSegments(segments: any[]) { //Create groups of segments that are similar to eachother //Segments must be sorted by their startTime so that we can build groups chronologically: //1. As long as the segments' startTime fall inside the currentGroup, we keep adding them to that group //2. If a segment starts after the end of the currentGroup (> cursor), no other segment will ever fall // inside that group (because they're sorted) so we can create a new one - const similarSegmentsGroups = []; - let currentGroup; + const similarSegmentsGroups: any[] = []; + let currentGroup: any; let cursor = -1; //-1 to make sure that, even if the 1st segment starts at 0, a new group is created segments.forEach(segment => { if (segment.startTime > cursor) { - currentGroup = { segments: [], votes: 0 }; + currentGroup = {segments: [], votes: 0}; similarSegmentsGroups.push(currentGroup); } @@ -136,7 +135,7 @@ function chooseSegments(segments) { //if there are too many groups, find the best 8 return getWeightedRandomChoice(similarSegmentsGroups, 8).map( //randomly choose 1 good segment per group and return them - group => getWeightedRandomChoice(group.segments, 1)[0] + group => getWeightedRandomChoice(group.segments, 1)[0], ); } @@ -150,15 +149,15 @@ function chooseSegments(segments) { * * @returns */ -function handleGetSegments(req, res) { - const videoID = req.query.videoID; +function handleGetSegments(req: Request, res: Response) { + const videoID = req.query.videoID as string; // Default to sponsor // If using params instead of JSON, only one category can be pulled const categories = req.query.categories - ? JSON.parse(req.query.categories) + ? JSON.parse(req.query.categories as string) : req.query.category - ? [req.query.category] - : ['sponsor']; + ? [req.query.category] + : ['sponsor']; let segments = cleanGetSegments(req, videoID, categories); @@ -175,15 +174,17 @@ function handleGetSegments(req, res) { return segments; } -module.exports = { +function endpoint(req: Request, res: Response) { + const segments = handleGetSegments(req, res); + + if (segments) { + //send result + res.send(segments); + } +} + +export { handleGetSegments, cleanGetSegments, - endpoint: function (req, res) { - let segments = handleGetSegments(req, res); - - if (segments) { - //send result - res.send(segments); - } - }, + endpoint, }; diff --git a/src/routes/getSkipSegmentsByHash.js b/src/routes/getSkipSegmentsByHash.ts similarity index 52% rename from src/routes/getSkipSegmentsByHash.js rename to src/routes/getSkipSegmentsByHash.ts index 28131b6..81f2daa 100644 --- a/src/routes/getSkipSegmentsByHash.js +++ b/src/routes/getSkipSegmentsByHash.ts @@ -1,11 +1,9 @@ -const hashPrefixTester = require('../utils/hashPrefixTester.js'); -const getSegments = require('./getSkipSegments.js').cleanGetSegments; +import {hashPrefixTester} from '../utils/hashPrefixTester'; +import {cleanGetSegments} from './getSkipSegments'; +import {db} from '../databases/databases'; +import {Request, Response} from 'express'; -const databases = require('../databases/databases.js'); -const logger = require('../utils/logger.js'); -const db = databases.db; - -module.exports = async function (req, res) { +export async function getSkipSegmentsByHash(req: Request, res: Response) { let hashPrefix = req.params.prefix; if (!hashPrefixTester(req.params.prefix)) { res.status(400).send("Hash prefix does not match format requirements."); // Exit early on faulty prefix @@ -13,21 +11,21 @@ module.exports = async function (req, res) { } const categories = req.query.categories - ? JSON.parse(req.query.categories) - : req.query.category - ? [req.query.category] - : ['sponsor']; + ? JSON.parse(req.query.categories as string) + : req.query.category + ? [req.query.category] + : ['sponsor']; // Get all video id's that match hash prefix - const videoIds = db.prepare('all', 'SELECT DISTINCT videoId, hashedVideoID from sponsorTimes WHERE hashedVideoID LIKE ?', [hashPrefix+'%']); + const videoIds = db.prepare('all', 'SELECT DISTINCT videoId, hashedVideoID from sponsorTimes WHERE hashedVideoID LIKE ?', [hashPrefix + '%']); - let segments = videoIds.map((video) => { + let segments = videoIds.map((video: any) => { return { videoID: video.videoID, hash: video.hashedVideoID, - segments: getSegments(req, video.videoID, categories) + segments: cleanGetSegments(req, video.videoID, categories), }; }); res.status((segments.length === 0) ? 404 : 200).json(segments); -} \ No newline at end of file +} diff --git a/src/routes/getTopUsers.js b/src/routes/getTopUsers.js deleted file mode 100644 index b78dad5..0000000 --- a/src/routes/getTopUsers.js +++ /dev/null @@ -1,92 +0,0 @@ -var db = require('../databases/databases.js').db; -const logger = require('../utils/logger.js'); -const createMemoryCache = require('../utils/createMemoryCache.js'); -const config = require('../config.js'); - -const MILLISECONDS_IN_MINUTE = 60000; -const getTopUsersWithCache = createMemoryCache(generateTopUsersStats, config.getTopUsersCacheTimeMinutes * MILLISECONDS_IN_MINUTE); - -function generateTopUsersStats(sortBy, categoryStatsEnabled = false) { - return new Promise((resolve, reject) => { - const userNames = []; - const viewCounts = []; - const totalSubmissions = []; - const minutesSaved = []; - const categoryStats = categoryStatsEnabled ? [] : undefined; - - let additionalFields = ''; - if (categoryStatsEnabled) { - additionalFields += "SUM(CASE WHEN category = 'sponsor' THEN 1 ELSE 0 END) as categorySponsor, " + - "SUM(CASE WHEN category = 'intro' THEN 1 ELSE 0 END) as categorySumIntro, " + - "SUM(CASE WHEN category = 'outro' THEN 1 ELSE 0 END) as categorySumOutro, " + - "SUM(CASE WHEN category = 'interaction' THEN 1 ELSE 0 END) as categorySumInteraction, " + - "SUM(CASE WHEN category = 'selfpromo' THEN 1 ELSE 0 END) as categorySelfpromo, " + - "SUM(CASE WHEN category = 'music_offtopic' THEN 1 ELSE 0 END) as categoryMusicOfftopic, "; - } - - const rows = db.prepare('all', "SELECT COUNT(*) as totalSubmissions, SUM(views) as viewCount," + - "SUM((sponsorTimes.endTime - sponsorTimes.startTime) / 60 * sponsorTimes.views) as minutesSaved, " + - "SUM(votes) as userVotes, " + - additionalFields + - "IFNULL(userNames.userName, sponsorTimes.userID) as userName FROM sponsorTimes LEFT JOIN userNames ON sponsorTimes.userID=userNames.userID " + - "LEFT JOIN privateDB.shadowBannedUsers ON sponsorTimes.userID=privateDB.shadowBannedUsers.userID " + - "WHERE sponsorTimes.votes > -1 AND sponsorTimes.shadowHidden != 1 AND privateDB.shadowBannedUsers.userID IS NULL " + - "GROUP BY IFNULL(userName, sponsorTimes.userID) HAVING userVotes > 20 " + - "ORDER BY " + sortBy + " DESC LIMIT 100", []); - - for (let i = 0; i < rows.length; i++) { - userNames[i] = rows[i].userName; - - viewCounts[i] = rows[i].viewCount; - totalSubmissions[i] = rows[i].totalSubmissions; - minutesSaved[i] = rows[i].minutesSaved; - if (categoryStatsEnabled) { - categoryStats[i] = [ - rows[i].categorySponsor, - rows[i].categorySumIntro, - rows[i].categorySumOutro, - rows[i].categorySumInteraction, - rows[i].categorySelfpromo, - rows[i].categoryMusicOfftopic, - ]; - } - } - - resolve({ - userNames, - viewCounts, - totalSubmissions, - minutesSaved, - categoryStats - }); - }); -} - -module.exports = async function getTopUsers (req, res) { - let sortType = req.query.sortType; - let categoryStatsEnabled = req.query.categoryStats; - - if (sortType == undefined) { - //invalid request - res.sendStatus(400); - return; - } - - //setup which sort type to use - let sortBy = ''; - if (sortType == 0) { - sortBy = 'minutesSaved'; - } else if (sortType == 1) { - sortBy = 'viewCount'; - } else if (sortType == 2) { - sortBy = 'totalSubmissions'; - } else { - //invalid request - return res.sendStatus(400); - } - - const stats = await getTopUsersWithCache(sortBy, categoryStatsEnabled); - - //send this result - res.send(stats); -} diff --git a/src/routes/getTopUsers.ts b/src/routes/getTopUsers.ts new file mode 100644 index 0000000..cf069a1 --- /dev/null +++ b/src/routes/getTopUsers.ts @@ -0,0 +1,92 @@ +import {db} from '../databases/databases'; +import {createMemoryCache} from '../utils/createMemoryCache'; +import {config} from '../config'; +import {Request, Response} from 'express'; + +const MILLISECONDS_IN_MINUTE = 60000; +const getTopUsersWithCache = createMemoryCache(generateTopUsersStats, config.getTopUsersCacheTimeMinutes * MILLISECONDS_IN_MINUTE); + +function generateTopUsersStats(sortBy: string, categoryStatsEnabled: boolean = false) { + return new Promise((resolve) => { + const userNames = []; + const viewCounts = []; + const totalSubmissions = []; + const minutesSaved = []; + const categoryStats: any[] = categoryStatsEnabled ? [] : undefined; + + let additionalFields = ''; + if (categoryStatsEnabled) { + additionalFields += "SUM(CASE WHEN category = 'sponsor' THEN 1 ELSE 0 END) as categorySponsor, " + + "SUM(CASE WHEN category = 'intro' THEN 1 ELSE 0 END) as categorySumIntro, " + + "SUM(CASE WHEN category = 'outro' THEN 1 ELSE 0 END) as categorySumOutro, " + + "SUM(CASE WHEN category = 'interaction' THEN 1 ELSE 0 END) as categorySumInteraction, " + + "SUM(CASE WHEN category = 'selfpromo' THEN 1 ELSE 0 END) as categorySelfpromo, " + + "SUM(CASE WHEN category = 'music_offtopic' THEN 1 ELSE 0 END) as categoryMusicOfftopic, "; + } + + const rows = db.prepare('all', "SELECT COUNT(*) as totalSubmissions, SUM(views) as viewCount," + + "SUM((sponsorTimes.endTime - sponsorTimes.startTime) / 60 * sponsorTimes.views) as minutesSaved, " + + "SUM(votes) as userVotes, " + + additionalFields + + "IFNULL(userNames.userName, sponsorTimes.userID) as userName FROM sponsorTimes LEFT JOIN userNames ON sponsorTimes.userID=userNames.userID " + + "LEFT JOIN privateDB.shadowBannedUsers ON sponsorTimes.userID=privateDB.shadowBannedUsers.userID " + + "WHERE sponsorTimes.votes > -1 AND sponsorTimes.shadowHidden != 1 AND privateDB.shadowBannedUsers.userID IS NULL " + + "GROUP BY IFNULL(userName, sponsorTimes.userID) HAVING userVotes > 20 " + + "ORDER BY " + sortBy + " DESC LIMIT 100", []); + + for (let i = 0; i < rows.length; i++) { + userNames[i] = rows[i].userName; + + viewCounts[i] = rows[i].viewCount; + totalSubmissions[i] = rows[i].totalSubmissions; + minutesSaved[i] = rows[i].minutesSaved; + if (categoryStatsEnabled) { + categoryStats[i] = [ + rows[i].categorySponsor, + rows[i].categorySumIntro, + rows[i].categorySumOutro, + rows[i].categorySumInteraction, + rows[i].categorySelfpromo, + rows[i].categoryMusicOfftopic, + ]; + } + } + + resolve({ + userNames, + viewCounts, + totalSubmissions, + minutesSaved, + categoryStats, + }); + }); +} + +export async function getTopUsers(req: Request, res: Response) { + const sortType = parseInt(req.query.sortType as string); + const categoryStatsEnabled = req.query.categoryStats; + + if (sortType == undefined) { + //invalid request + res.sendStatus(400); + return; + } + + //setup which sort type to use + let sortBy = ''; + if (sortType == 0) { + sortBy = 'minutesSaved'; + } else if (sortType == 1) { + sortBy = 'viewCount'; + } else if (sortType == 2) { + sortBy = 'totalSubmissions'; + } else { + //invalid request + return res.sendStatus(400); + } + + const stats = await getTopUsersWithCache(sortBy, categoryStatsEnabled); + + //send this result + res.send(stats); +} diff --git a/src/routes/getTotalStats.js b/src/routes/getTotalStats.ts similarity index 79% rename from src/routes/getTotalStats.js rename to src/routes/getTotalStats.ts index 6d3841e..d13d080 100644 --- a/src/routes/getTotalStats.js +++ b/src/routes/getTotalStats.ts @@ -1,19 +1,20 @@ -const db = require('../databases/databases.js').db; -const request = require('request'); -const config = require('../config.js'); +import {db} from '../databases/databases'; +import request from 'request'; +import {config} from '../config'; +import {Request, Response} from 'express'; // A cache of the number of chrome web store users -let chromeUsersCache = null; -let firefoxUsersCache = null; +let chromeUsersCache = 0; +let firefoxUsersCache = 0; // By the privacy friendly user counter -let apiUsersCache = null; +let apiUsersCache = 0; let lastUserCountCheck = 0; -module.exports = function getTotalStats (req, res) { +export function getTotalStats(req: Request, res: Response) { let row = db.prepare('get', "SELECT COUNT(DISTINCT userID) as userCount, COUNT(*) as totalSubmissions, " + - "SUM(views) as viewCount, SUM((endTime - startTime) / 60 * views) as minutesSaved FROM sponsorTimes WHERE shadowHidden != 1 AND votes >= 0", []); + "SUM(views) as viewCount, SUM((endTime - startTime) / 60 * views) as minutesSaved FROM sponsorTimes WHERE shadowHidden != 1 AND votes >= 0", []); if (row !== undefined) { let extensionUsers = chromeUsersCache + firefoxUsersCache; @@ -25,7 +26,7 @@ module.exports = function getTotalStats (req, res) { apiUsers: Math.max(apiUsersCache, extensionUsers), viewCount: row.viewCount, totalSubmissions: row.totalSubmissions, - minutesSaved: row.minutesSaved + minutesSaved: row.minutesSaved, }); // Check if the cache should be updated (every ~14 hours) @@ -49,7 +50,7 @@ function updateExtensionUsers() { try { firefoxUsersCache = parseInt(JSON.parse(body).average_daily_users); - request.get("https://chrome.google.com/webstore/detail/sponsorblock-for-youtube/mnjggcdmjocbbbhaepdhchncahnbgone", function(err, chromeResponse, body) { + request.get("https://chrome.google.com/webstore/detail/sponsorblock-for-youtube/mnjggcdmjocbbbhaepdhchncahnbgone", function (err, chromeResponse, body) { if (body !== undefined) { try { chromeUsersCache = parseInt(body.match(/(?<=\)/)[0].replace(",", "")); @@ -66,4 +67,4 @@ function updateExtensionUsers() { lastUserCountCheck = 0; } }); -} \ No newline at end of file +} diff --git a/src/routes/getUserInfo.js b/src/routes/getUserInfo.js deleted file mode 100644 index de17999..0000000 --- a/src/routes/getUserInfo.js +++ /dev/null @@ -1,82 +0,0 @@ -const db = require('../databases/databases.js').db; -const getHash = require('../utils/getHash.js'); - -function dbGetSubmittedSegmentSummary (userID) { - try { - let row = db.prepare("get", "SELECT SUM(((endTime - startTime) / 60) * views) as minutesSaved, count(*) as segmentCount FROM sponsorTimes WHERE userID = ? AND votes > -2 AND shadowHidden != 1", [userID]); - if (row.minutesSaved != null) { - return { - minutesSaved: row.minutesSaved, - segmentCount: row.segmentCount, - }; - } else { - return { - minutesSaved: 0, - segmentCount: 0, - }; - } - } catch (err) { - return false; - } -} - -function dbGetUsername (userID) { - try { - let row = db.prepare('get', "SELECT userName FROM userNames WHERE userID = ?", [userID]); - if (row !== undefined) { - return row.userName; - } else { - //no username yet, just send back the userID - return userID; - } - } catch (err) { - return false; - } -} - -function dbGetViewsForUser (userID) { - try { - let row = db.prepare('get', "SELECT SUM(views) as viewCount FROM sponsorTimes WHERE userID = ? AND votes > -2 AND shadowHidden != 1", [userID]); - //increase the view count by one - if (row.viewCount != null) { - return row.viewCount; - } else { - return 0; - } - } catch (err) { - return false; - } -} - -function dbGetWarningsForUser (userID) { - try { - let rows = db.prepare('all', "SELECT * FROM warnings WHERE userID = ?", [userID]); - return rows.length; - } catch (err) { - logger.error('Couldn\'t get warnings for user ' + userID + '. returning 0') ; - return 0; - } -} - -module.exports = function getUserInfo (req, res) { - let userID = req.query.userID; - - if (userID == undefined) { - //invalid request - res.status(400).send('Parameters are not valid'); - return; - } - - //hash the userID - userID = getHash(userID); - - const segmentsSummary = dbGetSubmittedSegmentSummary(userID); - res.send({ - userID, - userName: dbGetUsername(userID), - minutesSaved: segmentsSummary.minutesSaved, - segmentCount: segmentsSummary.segmentCount, - viewCount: dbGetViewsForUser(userID), - warnings: dbGetWarningsForUser(userID) - }); -} diff --git a/src/routes/getUserInfo.ts b/src/routes/getUserInfo.ts new file mode 100644 index 0000000..99c7694 --- /dev/null +++ b/src/routes/getUserInfo.ts @@ -0,0 +1,84 @@ +import {db} from '../databases/databases'; +import {getHash} from '../utils/getHash'; +import {Request, Response} from 'express'; +import {Logger} from '../utils/logger' + +function dbGetSubmittedSegmentSummary(userID: string): any { + try { + let row = db.prepare("get", "SELECT SUM(((endTime - startTime) / 60) * views) as minutesSaved, count(*) as segmentCount FROM sponsorTimes WHERE userID = ? AND votes > -2 AND shadowHidden != 1", [userID]); + if (row.minutesSaved != null) { + return { + minutesSaved: row.minutesSaved, + segmentCount: row.segmentCount, + }; + } else { + return { + minutesSaved: 0, + segmentCount: 0, + }; + } + } catch (err) { + return false; + } +} + +function dbGetUsername(userID: string) { + try { + let row = db.prepare('get', "SELECT userName FROM userNames WHERE userID = ?", [userID]); + if (row !== undefined) { + return row.userName; + } else { + //no username yet, just send back the userID + return userID; + } + } catch (err) { + return false; + } +} + +function dbGetViewsForUser(userID: string) { + try { + let row = db.prepare('get', "SELECT SUM(views) as viewCount FROM sponsorTimes WHERE userID = ? AND votes > -2 AND shadowHidden != 1", [userID]); + //increase the view count by one + if (row.viewCount != null) { + return row.viewCount; + } else { + return 0; + } + } catch (err) { + return false; + } +} + +function dbGetWarningsForUser(userID: string): number { + try { + let rows = db.prepare('all', "SELECT * FROM warnings WHERE userID = ?", [userID]); + return rows.length; + } catch (err) { + Logger.error('Couldn\'t get warnings for user ' + userID + '. returning 0'); + return 0; + } +} + +export function getUserInfo(req: Request, res: Response) { + let userID = req.query.userID as string; + + if (userID == undefined) { + //invalid request + res.status(400).send('Parameters are not valid'); + return; + } + + //hash the userID + userID = getHash(userID); + + const segmentsSummary = dbGetSubmittedSegmentSummary(userID); + res.send({ + userID, + userName: dbGetUsername(userID), + minutesSaved: segmentsSummary.minutesSaved, + segmentCount: segmentsSummary.segmentCount, + viewCount: dbGetViewsForUser(userID), + warnings: dbGetWarningsForUser(userID), + }); +} diff --git a/src/routes/getUsername.js b/src/routes/getUsername.ts similarity index 59% rename from src/routes/getUsername.js rename to src/routes/getUsername.ts index d04b8ee..3d78de9 100644 --- a/src/routes/getUsername.js +++ b/src/routes/getUsername.ts @@ -1,10 +1,10 @@ -var db = require('../databases/databases.js').db; +import {db} from '../databases/databases'; +import {getHash} from '../utils/getHash'; +import {Logger} from '../utils/logger'; +import {Request, Response} from 'express'; -var getHash = require('../utils/getHash.js'); -const logger = require('../utils/logger.js'); - -module.exports = function getUsername (req, res) { - let userID = req.query.userID; +export function getUsername(req: Request, res: Response) { + let userID = req.query.userID as string; if (userID == undefined) { //invalid request @@ -20,18 +20,18 @@ module.exports = function getUsername (req, res) { if (row !== undefined) { res.send({ - userName: row.userName + userName: row.userName, }); } else { //no username yet, just send back the userID res.send({ - userName: userID + userName: userID, }); } } catch (err) { - logger.error(err); + Logger.error(err); res.sendStatus(500); return; } -} \ No newline at end of file +} diff --git a/src/routes/getViewsForUser.js b/src/routes/getViewsForUser.js deleted file mode 100644 index 79d8961..0000000 --- a/src/routes/getViewsForUser.js +++ /dev/null @@ -1,33 +0,0 @@ -var db = require('../databases/databases.js').db; -var getHash = require('../utils/getHash.js'); -var logger = require('../utils/logger.js'); -module.exports = function getViewsForUser(req, res) { - let userID = req.query.userID; - - if (userID == undefined) { - //invalid request - res.sendStatus(400); - return; - } - - //hash the userID - userID = getHash(userID); - - try { - let row = db.prepare('get', "SELECT SUM(views) as viewCount FROM sponsorTimes WHERE userID = ?", [userID]); - - //increase the view count by one - if (row.viewCount != null) { - res.send({ - viewCount: row.viewCount - }); - } else { - res.sendStatus(404); - } - } catch (err) { - logger.error(err); - res.sendStatus(500); - - return; - } -} \ No newline at end of file diff --git a/src/routes/getViewsForUser.ts b/src/routes/getViewsForUser.ts new file mode 100644 index 0000000..1237100 --- /dev/null +++ b/src/routes/getViewsForUser.ts @@ -0,0 +1,35 @@ +import {db} from '../databases/databases'; +import {Request, Response} from 'express'; +import {getHash} from '../utils/getHash'; +import {Logger} from '../utils/logger'; + +export function getViewsForUser(req: Request, res: Response) { + let userID = req.query.userID as string; + + if (userID == undefined) { + //invalid request + res.sendStatus(400); + return; + } + + //hash the userID + userID = getHash(userID); + + try { + let row = db.prepare('get', "SELECT SUM(views) as viewCount FROM sponsorTimes WHERE userID = ?", [userID]); + + //increase the view count by one + if (row.viewCount != null) { + res.send({ + viewCount: row.viewCount, + }); + } else { + res.sendStatus(404); + } + } catch (err) { + Logger.error(err); + res.sendStatus(500); + + return; + } +} diff --git a/src/routes/oldGetVideoSponsorTimes.js b/src/routes/oldGetVideoSponsorTimes.ts similarity index 59% rename from src/routes/oldGetVideoSponsorTimes.js rename to src/routes/oldGetVideoSponsorTimes.ts index d898d38..a91173d 100644 --- a/src/routes/oldGetVideoSponsorTimes.js +++ b/src/routes/oldGetVideoSponsorTimes.ts @@ -1,10 +1,8 @@ -var getSkipSegments = require("./getSkipSegments.js") +import {handleGetSegments} from './getSkipSegments'; +import {Request, Response} from 'express'; - -module.exports = function (req, res) { - let videoID = req.query.videoID; - - let segments = getSkipSegments.handleGetSegments(req, res); +export function oldGetVideoSponsorTimes(req: Request, res: Response) { + let segments = handleGetSegments(req, res); if (segments) { // Convert to old outputs @@ -18,9 +16,9 @@ module.exports = function (req, res) { res.send({ sponsorTimes, - UUIDs - }) + UUIDs, + }); } // Error has already been handled in the other method -} \ No newline at end of file +} diff --git a/src/routes/oldSubmitSponsorTimes.js b/src/routes/oldSubmitSponsorTimes.js deleted file mode 100644 index 613ec9c..0000000 --- a/src/routes/oldSubmitSponsorTimes.js +++ /dev/null @@ -1,7 +0,0 @@ -var postSkipSegments = require('./postSkipSegments.js'); - -module.exports = async function submitSponsorTimes(req, res) { - req.query.category = "sponsor"; - - return postSkipSegments(req, res); -} diff --git a/src/routes/oldSubmitSponsorTimes.ts b/src/routes/oldSubmitSponsorTimes.ts new file mode 100644 index 0000000..bcf7934 --- /dev/null +++ b/src/routes/oldSubmitSponsorTimes.ts @@ -0,0 +1,8 @@ +import {postSkipSegments} from './postSkipSegments'; +import {Request, Response} from 'express'; + +export async function oldSubmitSponsorTimes(req: Request, res: Response) { + req.query.category = "sponsor"; + + return postSkipSegments(req, res); +} diff --git a/src/routes/postNoSegments.js b/src/routes/postNoSegments.ts similarity index 70% rename from src/routes/postNoSegments.js rename to src/routes/postNoSegments.ts index cdbfce9..04f6989 100644 --- a/src/routes/postNoSegments.js +++ b/src/routes/postNoSegments.ts @@ -1,23 +1,24 @@ -const db = require('../databases/databases.js').db; -const getHash = require('../utils/getHash.js'); -const isUserVIP = require('../utils/isUserVIP.js'); -const logger = require('../utils/logger.js'); +import {Logger} from '../utils/logger'; +import {getHash} from '../utils/getHash'; +import {isUserVIP} from '../utils/isUserVIP'; +import {db} from '../databases/databases'; +import {Request, Response} from 'express'; -module.exports = (req, res) => { +export function postNoSegments(req: Request, res: Response) { // Collect user input data let videoID = req.body.videoID; let userID = req.body.userID; let categories = req.body.categories; // Check input data is valid - if (!videoID - || !userID - || !categories - || !Array.isArray(categories) + if (!videoID + || !userID + || !categories + || !Array.isArray(categories) || categories.length === 0 ) { res.status(400).json({ - message: 'Bad Format' + message: 'Bad Format', }); return; } @@ -28,7 +29,7 @@ module.exports = (req, res) => { if (!userIsVIP) { res.status(403).json({ - message: 'Must be a VIP to mark videos.' + message: 'Must be a VIP to mark videos.', }); return; } @@ -38,7 +39,7 @@ module.exports = (req, res) => { if (!noSegmentList || noSegmentList.length === 0) { noSegmentList = []; } else { - noSegmentList = noSegmentList.map((obj) => { + noSegmentList = noSegmentList.map((obj: any) => { return obj.category; }); } @@ -60,15 +61,15 @@ module.exports = (req, res) => { try { db.prepare('run', "INSERT INTO noSegments (videoID, userID, category) VALUES(?, ?, ?)", [videoID, userID, category]); } catch (err) { - logger.error("Error submitting 'noSegment' marker for category '" + category + "' for video '" + videoID + "'"); - logger.error(err); + Logger.error("Error submitting 'noSegment' marker for category '" + category + "' for video '" + videoID + "'"); + Logger.error(err); res.status(500).json({ - message: "Internal Server Error: Could not write marker to the database." + message: "Internal Server Error: Could not write marker to the database.", }); } }); res.status(200).json({ - submitted: categoriesToMark - }); -}; \ No newline at end of file + submitted: categoriesToMark, + }); +} diff --git a/src/routes/postSegmentShift.js b/src/routes/postSegmentShift.js deleted file mode 100644 index fd8c568..0000000 --- a/src/routes/postSegmentShift.js +++ /dev/null @@ -1,101 +0,0 @@ -const db = require('../databases/databases.js').db; -const getHash = require('../utils/getHash.js'); -const isUserVIP = require('../utils/isUserVIP.js'); -const logger = require('../utils/logger.js'); - -const ACTION_NONE = Symbol('none'); -const ACTION_UPDATE = Symbol('update'); -const ACTION_REMOVE = Symbol('remove'); - -function shiftSegment(segment, shift) { - if (segment.startTime >= segment.endTime) return {action: ACTION_NONE, segment}; - if (shift.startTime >= shift.endTime) return {action: ACTION_NONE, segment}; - const duration = shift.endTime - shift.startTime; - if (shift.endTime < segment.startTime) { - // Scenario #1 cut before segment - segment.startTime -= duration; - segment.endTime -= duration; - return {action: ACTION_UPDATE, segment}; - } - if (shift.startTime > segment.endTime) { - // Scenario #2 cut after segment - return {action: ACTION_NONE, segment}; - } - if (segment.startTime < shift.startTime && segment.endTime > shift.endTime) { - // Scenario #3 cut inside segment - segment.endTime -= duration; - return {action: ACTION_UPDATE, segment}; - } - if (segment.startTime >= shift.startTime && segment.endTime > shift.endTime) { - // Scenario #4 cut overlap startTime - segment.startTime = shift.startTime; - segment.endTime -= duration; - return {action: ACTION_UPDATE, segment}; - } - if (segment.startTime < shift.startTime && segment.endTime <= shift.endTime) { - // Scenario #5 cut overlap endTime - segment.endTime = shift.startTime; - return {action: ACTION_UPDATE, segment}; - } - if (segment.startTime >= shift.startTime && segment.endTime <= shift.endTime) { - // Scenario #6 cut overlap startTime and endTime - return {action: ACTION_REMOVE, segment}; - } - return {action: ACTION_NONE, segment}; -} - -module.exports = (req, res) => { - // Collect user input data - const videoID = req.body.videoID; - const startTime = req.body.startTime; - const endTime = req.body.endTime; - let userID = req.body.userID; - - // Check input data is valid - if (!videoID - || !userID - || !startTime - || !endTime - ) { - res.status(400).json({ - message: 'Bad Format' - }); - return; - } - - // Check if user is VIP - userID = getHash(userID); - const userIsVIP = isUserVIP(userID); - - if (!userIsVIP) { - res.status(403).json({ - message: 'Must be a VIP to perform this action.' - }); - return; - } - - try { - const segments = db.prepare('all', 'SELECT startTime, endTime, UUID FROM sponsorTimes WHERE videoID = ?', [videoID]); - const shift = { - startTime, - endTime, - }; - segments.forEach(segment => { - const result = shiftSegment(segment, shift); - switch (result.action) { - case ACTION_UPDATE: - db.prepare('run', 'UPDATE sponsorTimes SET startTime = ?, endTime = ? WHERE UUID = ?', [result.segment.startTime, result.segment.endTime, result.segment.UUID]); - break; - case ACTION_REMOVE: - db.prepare('run', 'UPDATE sponsorTimes SET startTime = ?, endTime = ?, votes = -2 WHERE UUID = ?', [result.segment.startTime, result.segment.endTime, result.segment.UUID]); - break; - } - }); - } - catch(err) { - logger.error(err); - res.sendStatus(500); - } - - res.sendStatus(200); -}; diff --git a/src/routes/postSegmentShift.ts b/src/routes/postSegmentShift.ts new file mode 100644 index 0000000..a885b06 --- /dev/null +++ b/src/routes/postSegmentShift.ts @@ -0,0 +1,101 @@ +import {Request, Response} from 'express'; +import {Logger} from '../utils/logger'; +import {isUserVIP} from '../utils/isUserVIP'; +import {getHash} from '../utils/getHash'; +import {db} from '../databases/databases'; + +const ACTION_NONE = Symbol('none'); +const ACTION_UPDATE = Symbol('update'); +const ACTION_REMOVE = Symbol('remove'); + +function shiftSegment(segment: any, shift: { startTime: any; endTime: any }) { + if (segment.startTime >= segment.endTime) return {action: ACTION_NONE, segment}; + if (shift.startTime >= shift.endTime) return {action: ACTION_NONE, segment}; + const duration = shift.endTime - shift.startTime; + if (shift.endTime < segment.startTime) { + // Scenario #1 cut before segment + segment.startTime -= duration; + segment.endTime -= duration; + return {action: ACTION_UPDATE, segment}; + } + if (shift.startTime > segment.endTime) { + // Scenario #2 cut after segment + return {action: ACTION_NONE, segment}; + } + if (segment.startTime < shift.startTime && segment.endTime > shift.endTime) { + // Scenario #3 cut inside segment + segment.endTime -= duration; + return {action: ACTION_UPDATE, segment}; + } + if (segment.startTime >= shift.startTime && segment.endTime > shift.endTime) { + // Scenario #4 cut overlap startTime + segment.startTime = shift.startTime; + segment.endTime -= duration; + return {action: ACTION_UPDATE, segment}; + } + if (segment.startTime < shift.startTime && segment.endTime <= shift.endTime) { + // Scenario #5 cut overlap endTime + segment.endTime = shift.startTime; + return {action: ACTION_UPDATE, segment}; + } + if (segment.startTime >= shift.startTime && segment.endTime <= shift.endTime) { + // Scenario #6 cut overlap startTime and endTime + return {action: ACTION_REMOVE, segment}; + } + return {action: ACTION_NONE, segment}; +} + +export function postSegmentShift(req: Request, res: Response): Response { + // Collect user input data + const videoID = req.body.videoID; + const startTime = req.body.startTime; + const endTime = req.body.endTime; + let userID = req.body.userID; + + // Check input data is valid + if (!videoID + || !userID + || !startTime + || !endTime + ) { + res.status(400).json({ + message: 'Bad Format', + }); + return; + } + + // Check if user is VIP + userID = getHash(userID); + const userIsVIP = isUserVIP(userID); + + if (!userIsVIP) { + res.status(403).json({ + message: 'Must be a VIP to perform this action.', + }); + return; + } + + try { + const segments = db.prepare('all', 'SELECT startTime, endTime, UUID FROM sponsorTimes WHERE videoID = ?', [videoID]); + const shift = { + startTime, + endTime, + }; + segments.forEach((segment: any) => { + const result = shiftSegment(segment, shift); + switch (result.action) { + case ACTION_UPDATE: + db.prepare('run', 'UPDATE sponsorTimes SET startTime = ?, endTime = ? WHERE UUID = ?', [result.segment.startTime, result.segment.endTime, result.segment.UUID]); + break; + case ACTION_REMOVE: + db.prepare('run', 'UPDATE sponsorTimes SET startTime = ?, endTime = ?, votes = -2 WHERE UUID = ?', [result.segment.startTime, result.segment.endTime, result.segment.UUID]); + break; + } + }); + } catch (err) { + Logger.error(err); + res.sendStatus(500); + } + + res.sendStatus(200); +} diff --git a/src/routes/postSkipSegments.js b/src/routes/postSkipSegments.ts similarity index 58% rename from src/routes/postSkipSegments.js rename to src/routes/postSkipSegments.ts index f0af89b..d8c7469 100644 --- a/src/routes/postSkipSegments.js +++ b/src/routes/postSkipSegments.ts @@ -1,25 +1,23 @@ -const config = require('../config.js'); +import {config} from '../config'; +import {Logger} from '../utils/logger'; +import {db, privateDB} from '../databases/databases'; +import {YouTubeAPI} from '../utils/youtubeApi'; +import {getSubmissionUUID} from '../utils/getSubmissionUUID'; +import request from 'request'; +import isoDurations from 'iso8601-duration'; +import fetch from 'node-fetch'; +import {getHash} from '../utils/getHash'; +import {getIP} from '../utils/getIP'; +import {getFormattedTime} from '../utils/getFormattedTime'; +import {isUserTrustworthy} from '../utils/isUserTrustworthy'; +import {dispatchEvent} from '../utils/webhookUtils'; +import {Request, Response} from 'express'; -const databases = require('../databases/databases.js'); -const db = databases.db; -const privateDB = databases.privateDB; -const YouTubeAPI = require('../utils/youtubeAPI.js'); -const logger = require('../utils/logger.js'); -const getSubmissionUUID = require('../utils/getSubmissionUUID.js'); -const request = require('request'); -const isoDurations = require('iso8601-duration'); -const fetch = require('node-fetch'); -const getHash = require('../utils/getHash.js'); -const getIP = require('../utils/getIP.js'); -const getFormattedTime = require('../utils/getFormattedTime.js'); -const isUserTrustworthy = require('../utils/isUserTrustworthy.js') -const { dispatchEvent } = require('../utils/webhookUtils.js'); - -function sendWebhookNotification(userID, videoID, UUID, submissionCount, youtubeData, {submissionStart, submissionEnd}, segmentInfo) { - let row = db.prepare('get', "SELECT userName FROM userNames WHERE userID = ?", [userID]); - let userName = row !== undefined ? row.userName : null; - let video = youtubeData.items[0]; +function sendWebhookNotification(userID: string, videoID: string, UUID: string, submissionCount: number, youtubeData: any, {submissionStart, submissionEnd}: { submissionStart: number; submissionEnd: number; }, segmentInfo: any) { + const row = 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) { @@ -31,7 +29,7 @@ function sendWebhookNotification(userID, videoID, UUID, submissionCount, youtube "id": videoID, "title": video.snippet.title, "thumbnail": video.snippet.thumbnails.maxres ? video.snippet.thumbnails.maxres : null, - "url": "https://www.youtube.com/watch?v=" + videoID + "url": "https://www.youtube.com/watch?v=" + videoID, }, "submission": { "UUID": UUID, @@ -40,25 +38,28 @@ function sendWebhookNotification(userID, videoID, UUID, submissionCount, youtube "endTime": submissionEnd, "user": { "UUID": userID, - "username": userName - } - } + "username": userName, + }, + }, }); } -function sendWebhooks(userID, videoID, UUID, segmentInfo) { +function sendWebhooks(userID: string, videoID: string, UUID: string, segmentInfo: any) { if (config.youtubeAPIKey !== null) { - let userSubmissionCountRow = db.prepare('get', "SELECT count(*) as submissionCount FROM sponsorTimes WHERE userID = ?", [userID]); + const userSubmissionCountRow = db.prepare('get', "SELECT count(*) as submissionCount FROM sponsorTimes WHERE userID = ?", [userID]); - YouTubeAPI.listVideos(videoID, (err, data) => { + YouTubeAPI.listVideos(videoID, (err: any, data: any) => { if (err || data.items.length === 0) { - err && logger.error(err); + err && Logger.error(err); return; } - let startTime = parseFloat(segmentInfo.segment[0]); - let endTime = parseFloat(segmentInfo.segment[1]); - sendWebhookNotification(userID, videoID, UUID, userSubmissionCountRow.submissionCount, data, {submissionStart: startTime, submissionEnd: endTime}, segmentInfo); + const startTime = parseFloat(segmentInfo.segment[0]); + const endTime = parseFloat(segmentInfo.segment[1]); + sendWebhookNotification(userID, videoID, UUID, userSubmissionCountRow.submissionCount, data, { + submissionStart: startTime, + submissionEnd: endTime, + }, segmentInfo); // If it is a first time submission // Then send a notification to discord @@ -67,45 +68,45 @@ function sendWebhooks(userID, videoID, UUID, segmentInfo) { json: { "embeds": [{ "title": data.items[0].snippet.title, - "url": "https://www.youtube.com/watch?v=" + videoID + "&t=" + (startTime.toFixed(0) - 2), + "url": "https://www.youtube.com/watch?v=" + videoID + "&t=" + (parseInt(startTime.toFixed(0)) - 2), "description": "Submission ID: " + UUID + - "\n\nTimestamp: " + - getFormattedTime(startTime) + " to " + getFormattedTime(endTime) + - "\n\nCategory: " + segmentInfo.category, + "\n\nTimestamp: " + + getFormattedTime(startTime) + " to " + getFormattedTime(endTime) + + "\n\nCategory: " + segmentInfo.category, "color": 10813440, "author": { - "name": userID + "name": userID, }, "thumbnail": { "url": data.items[0].snippet.thumbnails.maxres ? data.items[0].snippet.thumbnails.maxres.url : "", - } - }] - } + }, + }], + }, }, (err, res) => { - if (err) { - logger.error("Failed to send first time submission Discord hook."); - logger.error(JSON.stringify(err)); - logger.error("\n"); - } else if (res && res.statusCode >= 400) { - logger.error("Error sending first time submission Discord hook"); - logger.error(JSON.stringify(res)); - logger.error("\n"); - } + if (err) { + Logger.error("Failed to send first time submission Discord hook."); + Logger.error(JSON.stringify(err)); + Logger.error("\n"); + } else if (res && res.statusCode >= 400) { + Logger.error("Error sending first time submission Discord hook"); + Logger.error(JSON.stringify(res)); + Logger.error("\n"); + } }); }); } } -function sendWebhooksNB(userID, videoID, UUID, startTime, endTime, category, probability, ytData) { - let submissionInfoRow = db.prepare('get', "SELECT " + +function sendWebhooksNB(userID: string, videoID: string, UUID: string, startTime: number, endTime: number, category: string, probability: number, ytData: any) { + const submissionInfoRow = db.prepare('get', "SELECT " + "(select count(1) from sponsorTimes where userID = ?) count, " + "(select count(1) from sponsorTimes where userID = ? and votes <= -2) disregarded, " + "coalesce((select userName FROM userNames WHERE userID = ?), ?) userName", [userID, userID, userID, userID]); - let submittedBy = ""; + let submittedBy: string; // If a userName was created then show both - if (submissionInfoRow.userName !== userID){ + if (submissionInfoRow.userName !== userID) { submittedBy = submissionInfoRow.userName + "\n " + userID; } else { submittedBy = userID; @@ -117,30 +118,30 @@ function sendWebhooksNB(userID, videoID, UUID, startTime, endTime, category, pro json: { "embeds": [{ "title": ytData.items[0].snippet.title, - "url": "https://www.youtube.com/watch?v=" + videoID + "&t=" + (startTime.toFixed(0) - 2), + "url": "https://www.youtube.com/watch?v=" + videoID + "&t=" + (parseFloat(startTime.toFixed(0)) - 2), "description": "**Submission ID:** " + UUID + - "\n**Timestamp:** " + getFormattedTime(startTime) + " to " + getFormattedTime(endTime) + - "\n**Predicted Probability:** " + probability + - "\n**Category:** " + category + - "\n**Submitted by:** "+ submittedBy + - "\n**Total User Submissions:** "+submissionInfoRow.count + - "\n**Ignored User Submissions:** "+submissionInfoRow.disregarded, + "\n**Timestamp:** " + getFormattedTime(startTime) + " to " + getFormattedTime(endTime) + + "\n**Predicted Probability:** " + probability + + "\n**Category:** " + category + + "\n**Submitted by:** " + submittedBy + + "\n**Total User Submissions:** " + submissionInfoRow.count + + "\n**Ignored User Submissions:** " + submissionInfoRow.disregarded, "color": 10813440, "thumbnail": { "url": ytData.items[0].snippet.thumbnails.maxres ? ytData.items[0].snippet.thumbnails.maxres.url : "", - } - }] - } + }, + }], + }, }, (err, res) => { - if (err) { - logger.error("Failed to send NeuralBlock Discord hook."); - logger.error(JSON.stringify(err)); - logger.error("\n"); - } else if (res && res.statusCode >= 400) { - logger.error("Error sending NeuralBlock Discord hook"); - logger.error(JSON.stringify(res)); - logger.error("\n"); - } + if (err) { + Logger.error("Failed to send NeuralBlock Discord hook."); + Logger.error(JSON.stringify(err)); + Logger.error("\n"); + } else if (res && res.statusCode >= 400) { + Logger.error("Error sending NeuralBlock Discord hook"); + Logger.error(JSON.stringify(res)); + Logger.error("\n"); + } }); } @@ -150,11 +151,11 @@ function sendWebhooksNB(userID, videoID, UUID, startTime, endTime, category, pro // Looks like this was broken for no defined youtube key - fixed but IMO we shouldn't return // false for a pass - it was confusing and lead to this bug - any use of this function in // the future could have the same problem. -async function autoModerateSubmission(submission) { +async function autoModerateSubmission(submission: { videoID: any; userID: any; segments: any }) { // Get the video information from the youtube API if (config.youtubeAPIKey !== null) { - let {err, data} = await new Promise((resolve, reject) => { - YouTubeAPI.listVideos(submission.videoID, (err, data) => resolve({err, data})); + const {err, data} = await new Promise((resolve) => { + YouTubeAPI.listVideos(submission.videoID, (err: any, data: any) => resolve({err, data})); }); if (err) { @@ -164,54 +165,54 @@ async function autoModerateSubmission(submission) { if (data.pageInfo.totalResults === 0) { return "No video exists with id " + submission.videoID; } else { - let segments = submission.segments; + const segments = submission.segments; let nbString = ""; for (let i = 0; i < segments.length; i++) { - let startTime = parseFloat(segments[i].segment[0]); - let endTime = parseFloat(segments[i].segment[1]); + const startTime = parseFloat(segments[i].segment[0]); + const endTime = parseFloat(segments[i].segment[1]); let duration = data.items[0].contentDetails.duration; duration = isoDurations.toSeconds(isoDurations.parse(duration)); if (duration == 0) { // Allow submission if the duration is 0 (bug in youtube api) return false; - } else if ((endTime - startTime) > (duration/100)*80) { + } else if ((endTime - startTime) > (duration / 100) * 80) { // Reject submission if over 80% of the video return "One of your submitted segments is over 80% of the video."; } else { if (segments[i].category === "sponsor") { - //Prepare timestamps to send to NB all at once - nbString = nbString + segments[i].segment[0] + "," + segments[i].segment[1] + ";"; + //Prepare timestamps to send to NB all at once + nbString = nbString + segments[i].segment[0] + "," + segments[i].segment[1] + ";"; } } } // Check NeuralBlock - let neuralBlockURL = config.neuralBlockURL; + const neuralBlockURL = config.neuralBlockURL; if (!neuralBlockURL) return false; - let response = await fetch(neuralBlockURL + "/api/checkSponsorSegments?vid=" + submission.videoID + - "&segments=" + nbString.substring(0,nbString.length-1)); + const response = await fetch(neuralBlockURL + "/api/checkSponsorSegments?vid=" + submission.videoID + + "&segments=" + nbString.substring(0, nbString.length - 1)); if (!response.ok) return false; - let nbPredictions = await response.json(); - nbDecision = false; + const nbPredictions = await response.json(); + let nbDecision = false; let predictionIdx = 0; //Keep track because only sponsor categories were submitted - for (let i = 0; i < segments.length; i++){ - if (segments[i].category === "sponsor"){ - if (nbPredictions.probabilities[predictionIdx] < 0.70){ - nbDecision = true; // At least one bad entry - startTime = parseFloat(segments[i].segment[0]); - endTime = parseFloat(segments[i].segment[1]); + for (let i = 0; i < segments.length; i++) { + if (segments[i].category === "sponsor") { + if (nbPredictions.probabilities[predictionIdx] < 0.70) { + nbDecision = true; // At least one bad entry + const startTime = parseFloat(segments[i].segment[0]); + const endTime = parseFloat(segments[i].segment[1]); - const UUID = getSubmissionUUID(submission.videoID, segments[i].category, submission.userID, startTime, endTime); - // Send to Discord - // Note, if this is too spammy. Consider sending all the segments as one Webhook - sendWebhooksNB(submission.userID, submission.videoID, UUID, startTime, endTime, segments[i].category, nbPredictions.probabilities[predictionIdx], data); + const UUID = getSubmissionUUID(submission.videoID, segments[i].category, submission.userID, startTime, endTime); + // Send to Discord + // Note, if this is too spammy. Consider sending all the segments as one Webhook + sendWebhooksNB(submission.userID, submission.videoID, UUID, startTime, endTime, segments[i].category, nbPredictions.probabilities[predictionIdx], data); } predictionIdx++; } } - if (nbDecision){ + if (nbDecision) { return "Rejected based on NeuralBlock predictions."; } else { return false; @@ -219,7 +220,7 @@ async function autoModerateSubmission(submission) { } } } else { - logger.debug("Skipped YouTube API"); + Logger.debug("Skipped YouTube API"); // Can't moderate the submission without calling the youtube API // so allow by default. @@ -227,33 +228,33 @@ async function autoModerateSubmission(submission) { } } -function proxySubmission(req) { - request.post(config.proxySubmission + '/api/skipSegments?userID='+req.query.userID+'&videoID='+req.query.videoID, {json: req.body}, (err, result) => { +function proxySubmission(req: Request) { + request.post(config.proxySubmission + '/api/skipSegments?userID=' + req.query.userID + '&videoID=' + req.query.videoID, {json: req.body}, (err, result) => { if (config.mode === 'development') { if (!err) { - logger.debug('Proxy Submission: ' + result.statusCode + ' ('+result.body+')'); + Logger.debug('Proxy Submission: ' + result.statusCode + ' (' + result.body + ')'); } else { - logger.error("Proxy Submission: Failed to make call"); + Logger.error("Proxy Submission: Failed to make call"); } } }); } -module.exports = async function postSkipSegments(req, res) { +export async function postSkipSegments(req: Request, res: Response) { if (config.proxySubmission) { proxySubmission(req); } - let videoID = req.query.videoID || req.body.videoID; + const videoID = req.query.videoID || req.body.videoID; let userID = req.query.userID || req.body.userID; - let segments = req.body.segments; + let segments = req.body.segments; if (segments === undefined) { // Use query instead segments = [{ segment: [req.query.startTime, req.query.endTime], - category: req.query.category + category: req.query.category, }]; } @@ -279,24 +280,26 @@ module.exports = async function postSkipSegments(req, res) { userID = getHash(userID); //hash the ip 5000 times so no one can get it from the database - let hashedIP = getHash(getIP(req) + config.globalSalt); - + const hashedIP = getHash(getIP(req) + config.globalSalt); + const MILLISECONDS_IN_HOUR = 3600000; const now = Date.now(); - let warningsCount = db.prepare('get', "SELECT count(1) as count FROM warnings WHERE userID = ? AND issueTime > ?", - [userID, Math.floor(now - (config.hoursAfterWarningExpires * MILLISECONDS_IN_HOUR))] + const warningsCount = db.prepare('get', "SELECT count(1) as count FROM warnings WHERE userID = ? AND issueTime > ?", + [userID, Math.floor(now - (config.hoursAfterWarningExpires * MILLISECONDS_IN_HOUR))], ).count; - + if (warningsCount >= config.maxNumberOfActiveWarnings) { - return res.status(403).send('Submission blocked. Too many active warnings!'); + return res.status(403).send('Submission blocked. Too many active warnings!'); } - let noSegmentList = db.prepare('all', 'SELECT category from noSegments where videoID = ?', [videoID]).map((list) => { return list.category }); - - //check if this user is on the vip list - let isVIP = db.prepare("get", "SELECT count(*) as userCount FROM vipUsers WHERE userID = ?", [userID]).userCount > 0; + const noSegmentList = db.prepare('all', 'SELECT category from noSegments where videoID = ?', [videoID]).map((list: any) => { + return list.category; + }); - let decreaseVotes = 0; + //check if this user is on the vip list + const isVIP = db.prepare("get", "SELECT count(*) as userCount FROM vipUsers WHERE userID = ?", [userID]).userCount > 0; + + const decreaseVotes = 0; // Check if all submissions are correct for (let i = 0; i < segments.length; i++) { @@ -305,32 +308,32 @@ module.exports = async function postSkipSegments(req, res) { res.status(400).send("One of your segments are invalid"); return; } - + if (!config.categoryList.includes(segments[i].category)) { - res.status("400").send("Category doesn't exist."); - return; + res.status(400).send("Category doesn't exist."); + return; } // Reject segemnt if it's in the no segments list if (!isVIP && noSegmentList.indexOf(segments[i].category) !== -1) { // TODO: Do something about the fradulent submission - logger.warn("Caught a no-segment submission. userID: '" + userID + "', videoID: '" + videoID + "', category: '" + segments[i].category + "'"); + Logger.warn("Caught a no-segment submission. userID: '" + userID + "', videoID: '" + videoID + "', category: '" + segments[i].category + "'"); res.status(403).send( - "Request rejected by auto moderator: New submissions are not allowed for the following category: '" - + segments[i].category + "'. A moderator has decided that no new segments are needed and that all current segments of this category are timed perfectly.\n\n " - + (segments[i].category === "sponsor" ? "Maybe the segment you are submitting is a different category that you have not enabled and is not a sponsor. " + - "Categories that aren't sponsor, such as self-promotion can be enabled in the options.\n\n " : "") - + "If you believe this is incorrect, please contact someone on Discord." + "Request rejected by auto moderator: New submissions are not allowed for the following category: '" + + segments[i].category + "'. A moderator has decided that no new segments are needed and that all current segments of this category are timed perfectly.\n\n " + + (segments[i].category === "sponsor" ? "Maybe the segment you are submitting is a different category that you have not enabled and is not a sponsor. " + + "Categories that aren't sponsor, such as self-promotion can be enabled in the options.\n\n " : "") + + "If you believe this is incorrect, please contact someone on Discord.", ); return; } - + let startTime = parseFloat(segments[i].segment[0]); let endTime = parseFloat(segments[i].segment[1]); if (isNaN(startTime) || isNaN(endTime) - || startTime === Infinity || endTime === Infinity || startTime < 0 || startTime >= endTime) { + || startTime === Infinity || endTime === Infinity || startTime < 0 || startTime >= endTime) { //invalid request res.status(400).send("One of your segments times are invalid (too short, startTime before endTime, etc.)"); return; @@ -343,7 +346,7 @@ module.exports = async function postSkipSegments(req, res) { } //check if this info has already been submitted before - let duplicateCheck2Row = db.prepare('get', "SELECT COUNT(*) as count FROM sponsorTimes WHERE startTime = ? " + + const duplicateCheck2Row = db.prepare('get', "SELECT COUNT(*) as count FROM sponsorTimes WHERE startTime = ? " + "and endTime = ? and category = ? and videoID = ?", [startTime, endTime, segments[i].category, videoID]); if (duplicateCheck2Row.count > 0) { res.sendStatus(409); @@ -353,8 +356,8 @@ module.exports = async function postSkipSegments(req, res) { // Auto moderator check if (!isVIP) { - let autoModerateResult = await autoModerateSubmission({userID, videoID, segments});//startTime, endTime, category: segments[i].category}); - if (autoModerateResult == "Rejected based on NeuralBlock predictions."){ + const autoModerateResult = await autoModerateSubmission({userID, videoID, segments});//startTime, endTime, category: segments[i].category}); + if (autoModerateResult == "Rejected based on NeuralBlock predictions.") { // If NB automod rejects, the submission will start with -2 votes. // Note, if one submission is bad all submissions will be affected. // However, this behavior is consistent with other automod functions @@ -367,18 +370,18 @@ module.exports = async function postSkipSegments(req, res) { } } // Will be filled when submitting - let UUIDs = []; + const UUIDs = []; try { //get current time - let timeSubmitted = Date.now(); + const timeSubmitted = Date.now(); - let yesterday = timeSubmitted - 86400000; + const yesterday = timeSubmitted - 86400000; // Disable IP ratelimiting for now if (false) { //check to see if this ip has submitted too many sponsors today - let rateLimitCheckRow = privateDB.prepare('get', "SELECT COUNT(*) as count FROM sponsorTimes WHERE hashedIP = ? AND videoID = ? AND timeSubmitted > ?", [hashedIP, videoID, yesterday]); + const rateLimitCheckRow = privateDB.prepare('get', "SELECT COUNT(*) as count FROM sponsorTimes WHERE hashedIP = ? AND videoID = ? AND timeSubmitted > ?", [hashedIP, videoID, yesterday]); if (rateLimitCheckRow.count >= 10) { //too many sponsors for the same video from the same ip address @@ -391,7 +394,7 @@ module.exports = async function postSkipSegments(req, res) { // Disable max submissions for now if (false) { //check to see if the user has already submitted sponsors for this video - let duplicateCheckRow = db.prepare('get', "SELECT COUNT(*) as count FROM sponsorTimes WHERE userID = ? and videoID = ?", [userID, videoID]); + const duplicateCheckRow = db.prepare('get', "SELECT COUNT(*) as count FROM sponsorTimes WHERE userID = ? and videoID = ?", [userID, videoID]); if (duplicateCheckRow.count >= 16) { //too many sponsors for the same video from the same user @@ -402,7 +405,7 @@ module.exports = async function postSkipSegments(req, res) { } //check to see if this user is shadowbanned - let shadowBanRow = privateDB.prepare('get', "SELECT count(*) as userCount FROM shadowBannedUsers WHERE userID = ?", [userID]); + const shadowBanRow = privateDB.prepare('get', "SELECT count(*) as userCount FROM shadowBannedUsers WHERE userID = ?", [userID]); let shadowBanned = shadowBanRow.userCount; @@ -418,20 +421,20 @@ module.exports = async function postSkipSegments(req, res) { } if (config.youtubeAPIKey !== null) { - let {err, data} = await new Promise((resolve, reject) => { - YouTubeAPI.listVideos(videoID, (err, data) => resolve({err, data})); + let {err, data} = await new Promise((resolve) => { + YouTubeAPI.listVideos(videoID, (err: any, data: any) => resolve({err, data})); }); if (err) { - logger.error("Error while submitting when connecting to YouTube API: " + err); + Logger.error("Error while submitting when connecting to YouTube API: " + err); } else { //get all segments for this video and user - let allSubmittedByUser = db.prepare('all', "SELECT startTime, endTime FROM sponsorTimes WHERE userID = ? and videoID = ? and votes > -1", [userID, videoID]); - let allSegmentTimes = []; + const allSubmittedByUser = db.prepare('all', "SELECT startTime, endTime FROM sponsorTimes WHERE userID = ? and videoID = ? and votes > -1", [userID, videoID]); + const allSegmentTimes = []; if (allSubmittedByUser !== undefined) { //add segments the user has previously submitted for (const segmentInfo of allSubmittedByUser) { - allSegmentTimes.push([parseFloat(segmentInfo.startTime), parseFloat(segmentInfo.endTime)]) + allSegmentTimes.push([parseFloat(segmentInfo.startTime), parseFloat(segmentInfo.endTime)]); } } @@ -443,7 +446,9 @@ module.exports = async function postSkipSegments(req, res) { } //merge all the times into non-overlapping arrays - const allSegmentsSorted = mergeTimeSegments(allSegmentTimes.sort(function(a, b) { return a[0]-b[0] || a[1]-b[1] })); + const allSegmentsSorted = mergeTimeSegments(allSegmentTimes.sort(function (a, b) { + return a[0] - b[0] || a[1] - b[1]; + })); let videoDuration = data.items[0].contentDetails.duration; videoDuration = isoDurations.toSeconds(isoDurations.parse(videoDuration)); @@ -451,7 +456,7 @@ module.exports = async function postSkipSegments(req, res) { let allSegmentDuration = 0; //sum all segment times together allSegmentsSorted.forEach(segmentInfo => allSegmentDuration += segmentInfo[1] - segmentInfo[0]); - if (allSegmentDuration > (videoDuration/100)*80) { + if (allSegmentDuration > (videoDuration / 100) * 80) { // Reject submission if all segments combine are over 80% of the video res.status(400).send("Total length of your submitted segments are over 80% of the video."); return; @@ -467,19 +472,19 @@ module.exports = async function postSkipSegments(req, res) { const UUID = getSubmissionUUID(videoID, segmentInfo.category, userID, segmentInfo.segment[0], segmentInfo.segment[1]); try { - db.prepare('run', "INSERT INTO sponsorTimes " + + db.prepare('run', "INSERT INTO sponsorTimes " + "(videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden, hashedVideoID)" + "VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ - videoID, segmentInfo.segment[0], segmentInfo.segment[1], startingVotes, UUID, userID, timeSubmitted, 0, segmentInfo.category, shadowBanned, getHash(videoID, 1) - ] + videoID, segmentInfo.segment[0], segmentInfo.segment[1], startingVotes, UUID, userID, timeSubmitted, 0, segmentInfo.category, shadowBanned, getHash(videoID, 1), + ], ); - + //add to private db as well privateDB.prepare('run', "INSERT INTO sponsorTimes VALUES(?, ?, ?)", [videoID, hashedIP, timeSubmitted]); } catch (err) { //a DB change probably occurred res.sendStatus(502); - logger.error("Error when putting sponsorTime in the DB: " + videoID + ", " + segmentInfo.segment[0] + ", " + + Logger.error("Error when putting sponsorTime in the DB: " + videoID + ", " + segmentInfo.segment[0] + ", " + segmentInfo.segment[1] + ", " + userID + ", " + segmentInfo.category + ". " + err); return; @@ -488,7 +493,7 @@ module.exports = async function postSkipSegments(req, res) { UUIDs.push(UUID); } } catch (err) { - logger.error(err); + Logger.error(err); res.sendStatus(500); @@ -516,8 +521,9 @@ module.exports = async function postSkipSegments(req, res) { // [50, 80], // [100, 150] // ] -function mergeTimeSegments(ranges) { - var result = [], last; +function mergeTimeSegments(ranges: number[][]) { + const result: number[][] = []; + let last: number[]; ranges.forEach(function (r) { if (!last || r[0] > last[1]) @@ -527,4 +533,4 @@ function mergeTimeSegments(ranges) { }); return result; -} \ No newline at end of file +} diff --git a/src/routes/postWarning.js b/src/routes/postWarning.ts similarity index 57% rename from src/routes/postWarning.js rename to src/routes/postWarning.ts index 797d307..288caf6 100644 --- a/src/routes/postWarning.js +++ b/src/routes/postWarning.ts @@ -1,9 +1,10 @@ -const db = require('../databases/databases.js').db; -const getHash = require('../utils/getHash.js'); -const isUserVIP = require('../utils/isUserVIP.js'); -const logger = require('../utils/logger.js'); +import {Request, Response} from 'express'; +import {Logger} from '../utils/logger'; +import {db} from '../databases/databases'; +import {isUserVIP} from '../utils/isUserVIP'; +import {getHash} from '../utils/getHash'; -module.exports = (req, res) => { +export function postWarning(req: Request, res: Response) { // Collect user input data let issuerUserID = getHash(req.body.issuerUserID); let userID = getHash(req.body.userID); @@ -11,14 +12,14 @@ module.exports = (req, res) => { // Ensure user is a VIP if (!isUserVIP(issuerUserID)) { - logger.debug("Permission violation: User " + issuerUserID + " attempted to warn user " + userID + "."); // maybe warn? + Logger.debug("Permission violation: User " + issuerUserID + " attempted to warn user " + userID + "."); // maybe warn? res.status(403).json({"message": "Not a VIP"}); return; } db.prepare('run', 'INSERT INTO warnings (userID, issueTime, issuerUserID) VALUES (?, ?, ?)', [userID, issueTime, issuerUserID]); res.status(200).json({ - message: "Warning issued to user '" + userID + "'." + message: "Warning issued to user '" + userID + "'.", }); -}; \ No newline at end of file +} diff --git a/src/routes/setUsername.js b/src/routes/setUsername.js deleted file mode 100644 index 7e59754..0000000 --- a/src/routes/setUsername.js +++ /dev/null @@ -1,60 +0,0 @@ - -var config = require('../config.js'); - -var db = require('../databases/databases.js').db; -var getHash = require('../utils/getHash.js'); -const logger = require('../utils/logger.js'); - - -module.exports = function setUsername(req, res) { - let userID = req.query.userID; - let userName = req.query.username; - - let adminUserIDInput = req.query.adminUserID; - - if (userID == undefined || userName == undefined || userID === "undefined" || userName.length > 64) { - //invalid request - res.sendStatus(400); - return; - } - - if (userName.includes("discord")) { - // Don't allow - res.sendStatus(200); - return; - } - - if (adminUserIDInput != undefined) { - //this is the admin controlling the other users account, don't hash the controling account's ID - adminUserIDInput = getHash(adminUserIDInput); - - if (adminUserIDInput != config.adminUserID) { - //they aren't the admin - res.sendStatus(403); - return; - } - } else { - //hash the userID - userID = getHash(userID); - } - - try { - //check if username is already set - let row = db.prepare('get', "SELECT count(*) as count FROM userNames WHERE userID = ?", [userID]); - - if (row.count > 0) { - //already exists, update this row - db.prepare('run', "UPDATE userNames SET userName = ? WHERE userID = ?", [userName, userID]); - } else { - //add to the db - db.prepare('run', "INSERT INTO userNames VALUES(?, ?)", [userID, userName]); - } - - res.sendStatus(200); - } catch (err) { - logger.error(err); - res.sendStatus(500); - - return; - } -} \ No newline at end of file diff --git a/src/routes/setUsername.ts b/src/routes/setUsername.ts new file mode 100644 index 0000000..228a1b1 --- /dev/null +++ b/src/routes/setUsername.ts @@ -0,0 +1,58 @@ +import {config} from '../config'; +import {Logger} from '../utils/logger'; +import {db} from '../databases/databases'; +import {getHash} from '../utils/getHash'; +import {Request, Response} from 'express'; + +export function setUsername(req: Request, res: Response) { + let userID = req.query.userID as string; + let userName = req.query.username as string; + + let adminUserIDInput = req.query.adminUserID as string; + + if (userID == undefined || userName == undefined || userID === "undefined" || userName.length > 64) { + //invalid request + res.sendStatus(400); + return; + } + + if (userName.includes("discord")) { + // Don't allow + res.sendStatus(200); + return; + } + + if (adminUserIDInput != undefined) { + //this is the admin controlling the other users account, don't hash the controling account's ID + adminUserIDInput = getHash(adminUserIDInput); + + if (adminUserIDInput != config.adminUserID) { + //they aren't the admin + res.sendStatus(403); + return; + } + } else { + //hash the userID + userID = getHash(userID); + } + + try { + //check if username is already set + let row = db.prepare('get', "SELECT count(*) as count FROM userNames WHERE userID = ?", [userID]); + + if (row.count > 0) { + //already exists, update this row + db.prepare('run', "UPDATE userNames SET userName = ? WHERE userID = ?", [userName, userID]); + } else { + //add to the db + db.prepare('run', "INSERT INTO userNames VALUES(?, ?)", [userID, userName]); + } + + res.sendStatus(200); + } catch (err) { + Logger.error(err); + res.sendStatus(500); + + return; + } +} diff --git a/src/routes/shadowBanUser.js b/src/routes/shadowBanUser.js deleted file mode 100644 index 3617a94..0000000 --- a/src/routes/shadowBanUser.js +++ /dev/null @@ -1,92 +0,0 @@ -var databases = require('../databases/databases.js'); -var db = databases.db; -var privateDB = databases.privateDB; - -var getHash = require('../utils/getHash.js'); - -module.exports = async function shadowBanUser(req, res) { - let userID = req.query.userID; - let hashedIP = req.query.hashedIP; - let adminUserIDInput = req.query.adminUserID; - - let enabled = req.query.enabled; - if (enabled === undefined){ - enabled = true; - } else { - enabled = enabled === "true"; - } - - //if enabled is false and the old submissions should be made visible again - let unHideOldSubmissions = req.query.unHideOldSubmissions !== "false"; - - if (adminUserIDInput == undefined || (userID == undefined && hashedIP == undefined)) { - //invalid request - res.sendStatus(400); - return; - } - - //hash the userID - adminUserIDInput = getHash(adminUserIDInput); - - let isVIP = db.prepare("get", "SELECT count(*) as userCount FROM vipUsers WHERE userID = ?", [adminUserIDInput]).userCount > 0; - if (!isVIP) { - //not authorized - res.sendStatus(403); - return; - } - - if (userID) { - //check to see if this user is already shadowbanned - let row = privateDB.prepare('get', "SELECT count(*) as userCount FROM shadowBannedUsers WHERE userID = ?", [userID]); - - if (enabled && row.userCount == 0) { - //add them to the shadow ban list - - //add it to the table - privateDB.prepare('run', "INSERT INTO shadowBannedUsers VALUES(?)", [userID]); - - //find all previous submissions and hide them - if (unHideOldSubmissions) { - db.prepare('run', "UPDATE sponsorTimes SET shadowHidden = 1 WHERE userID = ?", [userID]); - } - } else if (!enabled && row.userCount > 0) { - //remove them from the shadow ban list - privateDB.prepare('run', "DELETE FROM shadowBannedUsers WHERE userID = ?", [userID]); - - //find all previous submissions and unhide them - if (unHideOldSubmissions) { - db.prepare('run', "UPDATE sponsorTimes SET shadowHidden = 0 WHERE userID = ?", [userID]); - } - } - } else if (hashedIP) { - //check to see if this user is already shadowbanned - // let row = privateDB.prepare('get', "SELECT count(*) as userCount FROM shadowBannedIPs WHERE hashedIP = ?", [hashedIP]); - - // if (enabled && row.userCount == 0) { - if (enabled) { - //add them to the shadow ban list - - //add it to the table - // privateDB.prepare('run', "INSERT INTO shadowBannedIPs VALUES(?)", [hashedIP]); - - - - //find all previous submissions and hide them - if (unHideOldSubmissions) { - db.prepare('run', "UPDATE sponsorTimes SET shadowHidden = 1 WHERE timeSubmitted IN " + - "(SELECT privateDB.timeSubmitted FROM sponsorTimes LEFT JOIN privateDB.sponsorTimes as privateDB ON sponsorTimes.timeSubmitted=privateDB.timeSubmitted " + - "WHERE privateDB.hashedIP = ?)", [hashedIP]); - } - } else if (!enabled && row.userCount > 0) { - // //remove them from the shadow ban list - // privateDB.prepare('run', "DELETE FROM shadowBannedUsers WHERE userID = ?", [userID]); - - // //find all previous submissions and unhide them - // if (unHideOldSubmissions) { - // db.prepare('run', "UPDATE sponsorTimes SET shadowHidden = 0 WHERE userID = ?", [userID]); - // } - } - } - - res.sendStatus(200); -} \ No newline at end of file diff --git a/src/routes/shadowBanUser.ts b/src/routes/shadowBanUser.ts new file mode 100644 index 0000000..96084c4 --- /dev/null +++ b/src/routes/shadowBanUser.ts @@ -0,0 +1,89 @@ +import {db, privateDB} from '../databases/databases'; +import {getHash} from '../utils/getHash'; +import {Request, Response} from 'express'; + + +export async function shadowBanUser(req: Request, res: Response) { + const userID = req.query.userID as string; + const hashedIP = req.query.hashedIP as string; + let adminUserIDInput = req.query.adminUserID as string; + + const enabled = req.query.enabled === undefined + ? false + : req.query.enabled === 'true'; + + //if enabled is false and the old submissions should be made visible again + const unHideOldSubmissions = req.query.unHideOldSubmissions !== "false"; + + if (adminUserIDInput == undefined || (userID == undefined && hashedIP == undefined)) { + //invalid request + res.sendStatus(400); + return; + } + + //hash the userID + adminUserIDInput = getHash(adminUserIDInput); + + const isVIP = db.prepare("get", "SELECT count(*) as userCount FROM vipUsers WHERE userID = ?", [adminUserIDInput]).userCount > 0; + if (!isVIP) { + //not authorized + res.sendStatus(403); + return; + } + + if (userID) { + //check to see if this user is already shadowbanned + const row = privateDB.prepare('get', "SELECT count(*) as userCount FROM shadowBannedUsers WHERE userID = ?", [userID]); + + if (enabled && row.userCount == 0) { + //add them to the shadow ban list + + //add it to the table + privateDB.prepare('run', "INSERT INTO shadowBannedUsers VALUES(?)", [userID]); + + //find all previous submissions and hide them + if (unHideOldSubmissions) { + db.prepare('run', "UPDATE sponsorTimes SET shadowHidden = 1 WHERE userID = ?", [userID]); + } + } else if (!enabled && row.userCount > 0) { + //remove them from the shadow ban list + privateDB.prepare('run', "DELETE FROM shadowBannedUsers WHERE userID = ?", [userID]); + + //find all previous submissions and unhide them + if (unHideOldSubmissions) { + db.prepare('run', "UPDATE sponsorTimes SET shadowHidden = 0 WHERE userID = ?", [userID]); + } + } + } + // TODO TYPESCRIPT a lof unused code, wtf is happening + else if (hashedIP) { + //check to see if this user is already shadowbanned + // let row = privateDB.prepare('get', "SELECT count(*) as userCount FROM shadowBannedIPs WHERE hashedIP = ?", [hashedIP]); + + // if (enabled && row.userCount == 0) { + if (enabled) { + //add them to the shadow ban list + + //add it to the table + // privateDB.prepare('run', "INSERT INTO shadowBannedIPs VALUES(?)", [hashedIP]); + + + //find all previous submissions and hide them + if (unHideOldSubmissions) { + db.prepare('run', "UPDATE sponsorTimes SET shadowHidden = 1 WHERE timeSubmitted IN " + + "(SELECT privateDB.timeSubmitted FROM sponsorTimes LEFT JOIN privateDB.sponsorTimes as privateDB ON sponsorTimes.timeSubmitted=privateDB.timeSubmitted " + + "WHERE privateDB.hashedIP = ?)", [hashedIP]); + } + } /*else if (!enabled && row.userCount > 0) { + // //remove them from the shadow ban list + // privateDB.prepare('run', "DELETE FROM shadowBannedUsers WHERE userID = ?", [userID]); + + // //find all previous submissions and unhide them + // if (unHideOldSubmissions) { + // db.prepare('run', "UPDATE sponsorTimes SET shadowHidden = 0 WHERE userID = ?", [userID]); + // } + }*/ + } + + res.sendStatus(200); +} diff --git a/src/routes/viewedVideoSponsorTime.js b/src/routes/viewedVideoSponsorTime.js deleted file mode 100644 index fcc22ea..0000000 --- a/src/routes/viewedVideoSponsorTime.js +++ /dev/null @@ -1,16 +0,0 @@ -var db = require('../databases/databases.js').db; - -module.exports = function viewedVideoSponsorTime(req, res) { - let UUID = req.query.UUID; - - if (UUID == undefined) { - //invalid request - res.sendStatus(400); - return; - } - - //up the view count by one - db.prepare('run', "UPDATE sponsorTimes SET views = views + 1 WHERE UUID = ?", [UUID]); - - res.sendStatus(200); -} diff --git a/src/routes/viewedVideoSponsorTime.ts b/src/routes/viewedVideoSponsorTime.ts new file mode 100644 index 0000000..5d9dda6 --- /dev/null +++ b/src/routes/viewedVideoSponsorTime.ts @@ -0,0 +1,16 @@ +import {db} from '../databases/databases'; +import {Request, Response} from 'express'; + +export function viewedVideoSponsorTime(req: Request, res: Response): Response { + let UUID = req.query.UUID; + + if (UUID == undefined) { + //invalid request + return res.sendStatus(400); + } + + //up the view count by one + db.prepare('run', "UPDATE sponsorTimes SET views = views + 1 WHERE UUID = ?", [UUID]); + + return res.sendStatus(200); +} diff --git a/src/routes/voteOnSponsorTime.js b/src/routes/voteOnSponsorTime.ts similarity index 66% rename from src/routes/voteOnSponsorTime.js rename to src/routes/voteOnSponsorTime.ts index bfe5e0a..7d6b7de 100644 --- a/src/routes/voteOnSponsorTime.js +++ b/src/routes/voteOnSponsorTime.ts @@ -1,47 +1,47 @@ -var config = require('../config.js'); - -var getHash = require('../utils/getHash.js'); -var getIP = require('../utils/getIP.js'); -var getFormattedTime = require('../utils/getFormattedTime.js'); -var isUserTrustworthy = require('../utils/isUserTrustworthy.js'); -const { getVoteAuthor, getVoteAuthorRaw, dispatchEvent } = require('../utils/webhookUtils.js'); - -var databases = require('../databases/databases.js'); -var db = databases.db; -var privateDB = databases.privateDB; -var YouTubeAPI = require('../utils/youtubeAPI.js'); -var request = require('request'); -const logger = require('../utils/logger.js'); -const isUserVIP = require('../utils/isUserVIP.js'); +import {Request, Response} from 'express'; +import {Logger} from '../utils/logger'; +import {isUserVIP} from '../utils/isUserVIP'; +import request from 'request'; +import {YouTubeAPI} from '../utils/youtubeApi'; +import {db, privateDB} from '../databases/databases'; +import {dispatchEvent, getVoteAuthor, getVoteAuthorRaw} from '../utils/webhookUtils'; +import {isUserTrustworthy} from '../utils/isUserTrustworthy'; +import {getFormattedTime} from '../utils/getFormattedTime'; +import {getIP} from '../utils/getIP'; +import {getHash} from '../utils/getHash'; +import {config} from '../config'; const voteTypes = { normal: 0, - incorrect: 1 + incorrect: 1, +}; + +interface VoteData { + UUID: string; + nonAnonUserID: string; + voteTypeEnum: number; + isVIP: boolean; + isOwnSubmission: boolean; + row: { + votes: number; + views: number; + }; + category: string; + incrementAmount: number; + oldIncrementAmount: number; } -/** - * @param {Object} voteData - * @param {string} voteData.UUID - * @param {string} voteData.nonAnonUserID - * @param {number} voteData.voteTypeEnum - * @param {boolean} voteData.isVIP - * @param {boolean} voteData.isOwnSubmission - * @param voteData.row - * @param {string} voteData.category - * @param {number} voteData.incrementAmount - * @param {number} voteData.oldIncrementAmount - */ -function sendWebhooks(voteData) { - let submissionInfoRow = db.prepare('get', "SELECT s.videoID, s.userID, s.startTime, s.endTime, s.category, u.userName, " + +function sendWebhooks(voteData: VoteData) { + const submissionInfoRow = db.prepare('get', "SELECT s.videoID, s.userID, s.startTime, s.endTime, s.category, u.userName, " + "(select count(1) from sponsorTimes where userID = s.userID) count, " + "(select count(1) from sponsorTimes where userID = s.userID and votes <= -2) disregarded " + "FROM sponsorTimes s left join userNames u on s.userID = u.userID where s.UUID=?", - [voteData.UUID]); + [voteData.UUID]); - let userSubmissionCountRow = db.prepare('get', "SELECT count(*) as submissionCount FROM sponsorTimes WHERE userID = ?", [voteData.nonAnonUserID]); + const userSubmissionCountRow = db.prepare('get', "SELECT count(*) as submissionCount FROM sponsorTimes WHERE userID = ?", [voteData.nonAnonUserID]); if (submissionInfoRow !== undefined && userSubmissionCountRow != undefined) { - let webhookURL = null; + let webhookURL: string = null; if (voteData.voteTypeEnum === voteTypes.normal) { webhookURL = config.discordReportChannelWebhookURL; } else if (voteData.voteTypeEnum === voteTypes.incorrect) { @@ -51,20 +51,20 @@ function sendWebhooks(voteData) { if (config.youtubeAPIKey !== null) { YouTubeAPI.listVideos(submissionInfoRow.videoID, (err, data) => { if (err || data.items.length === 0) { - err && logger.error(err); + err && Logger.error(err.toString()); return; } - let isUpvote = voteData.incrementAmount > 0; + const isUpvote = voteData.incrementAmount > 0; // Send custom webhooks dispatchEvent(isUpvote ? "vote.up" : "vote.down", { "user": { - "status": getVoteAuthorRaw(userSubmissionCountRow.submissionCount, voteData.isVIP, voteData.isOwnSubmission) + "status": getVoteAuthorRaw(userSubmissionCountRow.submissionCount, voteData.isVIP, voteData.isOwnSubmission), }, "video": { "id": submissionInfoRow.videoID, "title": data.items[0].snippet.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": data.items[0].snippet.thumbnails.maxres ? data.items[0].snippet.thumbnails.maxres.url : "", }, "submission": { "UUID": voteData.UUID, @@ -77,51 +77,51 @@ function sendWebhooks(voteData) { "username": submissionInfoRow.userName, "submissions": { "total": submissionInfoRow.count, - "ignored": submissionInfoRow.disregarded - } - } + "ignored": submissionInfoRow.disregarded, + }, + }, }, "votes": { "before": voteData.row.votes, - "after": (voteData.row.votes + voteData.incrementAmount - voteData.oldIncrementAmount) - } + "after": (voteData.row.votes + voteData.incrementAmount - voteData.oldIncrementAmount), + }, }); - + // Send discord message if (webhookURL !== null && !isUpvote) { request.post(webhookURL, { json: { "embeds": [{ "title": data.items[0].snippet.title, - "url": "https://www.youtube.com/watch?v=" + submissionInfoRow.videoID + "url": "https://www.youtube.com/watch?v=" + submissionInfoRow.videoID + "&t=" + (submissionInfoRow.startTime.toFixed(0) - 2), - "description": "**" + voteData.row.votes + " Votes Prior | " + - (voteData.row.votes + voteData.incrementAmount - voteData.oldIncrementAmount) + " Votes Now | " + voteData.row.views - + " Views**\n\n**Submission ID:** " + voteData.UUID + "description": "**" + voteData.row.votes + " Votes Prior | " + + (voteData.row.votes + voteData.incrementAmount - voteData.oldIncrementAmount) + " Votes Now | " + voteData.row.views + + " Views**\n\n**Submission ID:** " + voteData.UUID + "\n**Category:** " + submissionInfoRow.category - + "\n\n**Submitted by:** "+submissionInfoRow.userName+"\n " + submissionInfoRow.userID - + "\n\n**Total User Submissions:** "+submissionInfoRow.count - + "\n**Ignored User Submissions:** "+submissionInfoRow.disregarded - +"\n\n**Timestamp:** " + - getFormattedTime(submissionInfoRow.startTime) + " to " + getFormattedTime(submissionInfoRow.endTime), + + "\n\n**Submitted by:** " + submissionInfoRow.userName + "\n " + submissionInfoRow.userID + + "\n\n**Total User Submissions:** " + submissionInfoRow.count + + "\n**Ignored User Submissions:** " + submissionInfoRow.disregarded + + "\n\n**Timestamp:** " + + getFormattedTime(submissionInfoRow.startTime) + " to " + getFormattedTime(submissionInfoRow.endTime), "color": 10813440, "author": { - "name": getVoteAuthor(userSubmissionCountRow.submissionCount, voteData.isVIP, voteData.isOwnSubmission) + "name": getVoteAuthor(userSubmissionCountRow.submissionCount, voteData.isVIP, voteData.isOwnSubmission), }, "thumbnail": { "url": data.items[0].snippet.thumbnails.maxres ? data.items[0].snippet.thumbnails.maxres.url : "", - } - }] - } + }, + }], + }, }, (err, res) => { if (err) { - logger.error("Failed to send reported submission Discord hook."); - logger.error(JSON.stringify(err)); - logger.error("\n"); + Logger.error("Failed to send reported submission Discord hook."); + Logger.error(JSON.stringify(err)); + Logger.error("\n"); } else if (res && res.statusCode >= 400) { - logger.error("Error sending reported submission Discord hook"); - logger.error(JSON.stringify(res)); - logger.error("\n"); + Logger.error("Error sending reported submission Discord hook"); + Logger.error(JSON.stringify(res)); + Logger.error("\n"); } }); } @@ -131,9 +131,9 @@ function sendWebhooks(voteData) { } } -function categoryVote(UUID, userID, isVIP, category, hashedIP, res) { +function categoryVote(UUID: string, userID: string, isVIP: boolean, category: any, hashedIP: string, res: Response) { // Check if they've already made a vote - let previousVoteInfo = privateDB.prepare('get', "select count(*) as votes, category from categoryVotes where UUID = ? and userID = ?", [UUID, userID]); + const previousVoteInfo = privateDB.prepare('get', "select count(*) as votes, category from categoryVotes where UUID = ? and userID = ?", [UUID, userID]); if (previousVoteInfo !== undefined && previousVoteInfo.category === category) { // Double vote, ignore @@ -141,21 +141,21 @@ function categoryVote(UUID, userID, isVIP, category, hashedIP, res) { return; } - let currentCategory = db.prepare('get', "select category from sponsorTimes where UUID = ?", [UUID]); + const currentCategory = db.prepare('get', "select category from sponsorTimes where UUID = ?", [UUID]); if (!currentCategory) { // Submission doesn't exist - res.status("400").send("Submission doesn't exist."); + res.status(400).send("Submission doesn't exist."); return; } - + if (!config.categoryList.includes(category)) { - res.status("400").send("Category doesn't exist."); - return; + res.status(400).send("Category doesn't exist."); + return; } - let timeSubmitted = Date.now(); + const timeSubmitted = Date.now(); - let voteAmount = isVIP ? 500 : 1; + const voteAmount = isVIP ? 500 : 1; // Add the vote if (db.prepare('get', "select count(*) as count from categoryVotes where UUID = ? and category = ?", [UUID, category]).count > 0) { @@ -167,7 +167,7 @@ function categoryVote(UUID, userID, isVIP, category, hashedIP, res) { } // Add the info into the private db - if (previousVoteInfo !== undefined) { + if (previousVoteInfo !== undefined) { // Reverse the previous vote db.prepare('run', "update categoryVotes set votes = votes - ? where UUID = ? and category = ?", [voteAmount, UUID, previousVoteInfo.category]); @@ -177,15 +177,15 @@ function categoryVote(UUID, userID, isVIP, category, hashedIP, res) { } // See if the submissions category is ready to change - let currentCategoryInfo = db.prepare("get", "select votes from categoryVotes where UUID = ? and category = ?", [UUID, currentCategory.category]); + const currentCategoryInfo = db.prepare("get", "select votes from categoryVotes where UUID = ? and category = ?", [UUID, currentCategory.category]); - let submissionInfo = db.prepare("get", "SELECT userID, timeSubmitted FROM sponsorTimes WHERE UUID = ?", [UUID]); - let isSubmissionVIP = submissionInfo && isUserVIP(submissionInfo.userID); - let startingVotes = isSubmissionVIP ? 10000 : 1; + const submissionInfo = db.prepare("get", "SELECT userID, timeSubmitted FROM sponsorTimes WHERE UUID = ?", [UUID]); + const isSubmissionVIP = submissionInfo && isUserVIP(submissionInfo.userID); + const startingVotes = isSubmissionVIP ? 10000 : 1; // Change this value from 1 in the future to make it harder to change categories // Done this way without ORs incase the value is zero - let currentCategoryCount = (currentCategoryInfo === undefined || currentCategoryInfo === null) ? startingVotes : currentCategoryInfo.votes; + const currentCategoryCount = (currentCategoryInfo === undefined || currentCategoryInfo === null) ? startingVotes : currentCategoryInfo.votes; // Add submission as vote if (!currentCategoryInfo && submissionInfo) { @@ -194,7 +194,7 @@ function categoryVote(UUID, userID, isVIP, category, hashedIP, res) { privateDB.prepare("run", "insert into categoryVotes (UUID, userID, hashedIP, category, timeSubmitted) values (?, ?, ?, ?, ?)", [UUID, submissionInfo.userID, "unknown", currentCategory.category, submissionInfo.timeSubmitted]); } - let nextCategoryCount = (previousVoteInfo.votes || 0) + voteAmount; + const nextCategoryCount = (previousVoteInfo.votes || 0) + voteAmount; //TODO: In the future, raise this number from zero to make it harder to change categories // VIPs change it every time @@ -206,11 +206,11 @@ function categoryVote(UUID, userID, isVIP, category, hashedIP, res) { res.sendStatus(200); } -async function voteOnSponsorTime(req, res) { - let UUID = req.query.UUID; - let userID = req.query.userID; - let type = req.query.type; - let category = req.query.category; +async function voteOnSponsorTime(req: Request, res: Response) { + const UUID = req.query.UUID as string; + let userID = req.query.userID as string; + let type = req.query.type !== undefined ? parseInt(req.query.type as string) : undefined; + const category = req.query.category as string; if (UUID === undefined || userID === undefined || (type === undefined && category === undefined)) { //invalid request @@ -219,51 +219,51 @@ async function voteOnSponsorTime(req, res) { } //hash the userID - let nonAnonUserID = getHash(userID); + const nonAnonUserID = getHash(userID); userID = getHash(userID + UUID); //x-forwarded-for if this server is behind a proxy - let ip = getIP(req); + const ip = getIP(req); //hash the ip 5000 times so no one can get it from the database - let hashedIP = getHash(ip + config.globalSalt); + const hashedIP = getHash(ip + config.globalSalt); //check if this user is on the vip list - let isVIP = db.prepare('get', "SELECT count(*) as userCount FROM vipUsers WHERE userID = ?", [nonAnonUserID]).userCount > 0; + const isVIP = db.prepare('get', "SELECT count(*) as userCount FROM vipUsers WHERE userID = ?", [nonAnonUserID]).userCount > 0; //check if user voting on own submission - let isOwnSubmission = db.prepare("get", "SELECT UUID as submissionCount FROM sponsorTimes where userID = ? AND UUID = ?", [nonAnonUserID, UUID]) !== undefined; - + const isOwnSubmission = db.prepare("get", "SELECT UUID as submissionCount FROM sponsorTimes where userID = ? AND UUID = ?", [nonAnonUserID, UUID]) !== undefined; + if (type === undefined && category !== undefined) { return categoryVote(UUID, nonAnonUserID, isVIP, category, hashedIP, res); } if (type == 1 && !isVIP && !isOwnSubmission) { // Check if upvoting hidden segment - let voteInfo = db.prepare('get', "SELECT votes FROM sponsorTimes WHERE UUID = ?", [UUID]); + const voteInfo = db.prepare('get', "SELECT votes FROM sponsorTimes WHERE UUID = ?", [UUID]); if (voteInfo && voteInfo.votes <= -2) { - res.status(403).send("Not allowed to upvote segment with too many downvotes unless you are VIP.") + res.status(403).send("Not allowed to upvote segment with too many downvotes unless you are VIP."); return; } } - + const MILLISECONDS_IN_HOUR = 3600000; const now = Date.now(); - let warningsCount = db.prepare('get', "SELECT count(1) as count FROM warnings WHERE userID = ? AND issueTime > ?", - [nonAnonUserID, Math.floor(now - (config.hoursAfterWarningExpires * MILLISECONDS_IN_HOUR))] + const warningsCount = db.prepare('get', "SELECT count(1) as count FROM warnings WHERE userID = ? AND issueTime > ?", + [nonAnonUserID, Math.floor(now - (config.hoursAfterWarningExpires * MILLISECONDS_IN_HOUR))], ).count; - + if (warningsCount >= config.maxNumberOfActiveWarnings) { - return res.status(403).send('Vote blocked. Too many active warnings!'); + return res.status(403).send('Vote blocked. Too many active warnings!'); } - let voteTypeEnum = (type == 0 || type == 1) ? voteTypes.normal : voteTypes.incorrect; + const voteTypeEnum = (type == 0 || type == 1) ? voteTypes.normal : voteTypes.incorrect; try { //check if vote has already happened - let votesRow = privateDB.prepare('get', "SELECT type FROM votes WHERE userID = ? AND UUID = ?", [userID, UUID]); - + const votesRow = privateDB.prepare('get', "SELECT type FROM votes WHERE userID = ? AND UUID = ?", [userID, UUID]); + //-1 for downvote, 1 for upvote. Maybe more depending on reputation in the future let incrementAmount = 0; let oldIncrementAmount = 0; @@ -308,12 +308,12 @@ async function voteOnSponsorTime(req, res) { } //check if the increment amount should be multiplied (downvotes have more power if there have been many views) - let row = db.prepare('get', "SELECT votes, views FROM sponsorTimes WHERE UUID = ?", [UUID]); + const row = db.prepare('get', "SELECT votes, views FROM sponsorTimes WHERE UUID = ?", [UUID]); if (voteTypeEnum === voteTypes.normal) { if ((isVIP || isOwnSubmission) && incrementAmount < 0) { //this user is a vip and a downvote - incrementAmount = - (row.votes + 2 - oldIncrementAmount); + incrementAmount = -(row.votes + 2 - oldIncrementAmount); type = incrementAmount; } } else if (voteTypeEnum == voteTypes.incorrect) { @@ -325,10 +325,10 @@ async function voteOnSponsorTime(req, res) { } // Only change the database if they have made a submission before and haven't voted recently - let ableToVote = isVIP - || (db.prepare("get", "SELECT userID FROM sponsorTimes WHERE userID = ?", [nonAnonUserID]) !== undefined - && privateDB.prepare("get", "SELECT userID FROM shadowBannedUsers WHERE userID = ?", [nonAnonUserID]) === undefined - && privateDB.prepare("get", "SELECT UUID FROM votes WHERE UUID = ? AND hashedIP = ? AND userID != ?", [UUID, hashedIP, userID]) === undefined); + const ableToVote = isVIP + || (db.prepare("get", "SELECT userID FROM sponsorTimes WHERE userID = ?", [nonAnonUserID]) !== undefined + && privateDB.prepare("get", "SELECT userID FROM shadowBannedUsers WHERE userID = ?", [nonAnonUserID]) === undefined + && privateDB.prepare("get", "SELECT UUID FROM votes WHERE UUID = ? AND hashedIP = ? AND userID != ?", [UUID, hashedIP, userID]) === undefined); if (ableToVote) { //update the votes table @@ -352,21 +352,21 @@ async function voteOnSponsorTime(req, res) { //for each positive vote, see if a hidden submission can be shown again if (incrementAmount > 0 && voteTypeEnum === voteTypes.normal) { //find the UUID that submitted the submission that was voted on - let submissionUserIDInfo = db.prepare('get', "SELECT userID FROM sponsorTimes WHERE UUID = ?", [UUID]); + const submissionUserIDInfo = db.prepare('get', "SELECT userID FROM sponsorTimes WHERE UUID = ?", [UUID]); if (!submissionUserIDInfo) { // They are voting on a non-existent submission res.status(400).send("Voting on a non-existent submission"); return; } - let submissionUserID = submissionUserIDInfo.userID; + const submissionUserID = submissionUserIDInfo.userID; //check if any submissions are hidden - let hiddenSubmissionsRow = db.prepare('get', "SELECT count(*) as hiddenSubmissions FROM sponsorTimes WHERE userID = ? AND shadowHidden > 0", [submissionUserID]); + const hiddenSubmissionsRow = db.prepare('get', "SELECT count(*) as hiddenSubmissions FROM sponsorTimes WHERE userID = ? AND shadowHidden > 0", [submissionUserID]); if (hiddenSubmissionsRow.hiddenSubmissions > 0) { //see if some of this users submissions should be visible again - + if (await isUserTrustworthy(submissionUserID)) { //they are trustworthy again, show 2 of their submissions again, if there are two to show db.prepare('run', "UPDATE sponsorTimes SET shadowHidden = 0 WHERE ROWID IN (SELECT ROWID FROM sponsorTimes WHERE userID = ? AND shadowHidden = 1 LIMIT 2)", [submissionUserID]); @@ -387,17 +387,16 @@ async function voteOnSponsorTime(req, res) { row, category, incrementAmount, - oldIncrementAmount + oldIncrementAmount, }); } } catch (err) { - logger.error(err); + Logger.error(err); res.status(500).json({error: 'Internal error creating segment vote'}); } } -module.exports = { - voteOnSponsorTime, - endpoint: voteOnSponsorTime +export { + voteOnSponsorTime, }; diff --git a/src/types/config.model.ts b/src/types/config.model.ts new file mode 100644 index 0000000..88af3d2 --- /dev/null +++ b/src/types/config.model.ts @@ -0,0 +1,52 @@ +import * as redis from 'redis'; + +export interface SBSConfig { + port: number; + mockPort?: number; + globalSalt: string; + adminUserID: string; + youtubeAPIKey?: string; + discordReportChannelWebhookURL?: string; + discordFirstTimeSubmissionsWebhookURL?: string; + discordCompletelyIncorrectReportWebhookURL?: string; + neuralBlockURL?: string; + discordNeuralBlockRejectWebhookURL?: string; + userCounterURL?: string; + proxySubmission?: string; + behindProxy: string | boolean; + db: string; + privateDB: string; + createDatabaseIfNotExist: boolean; + schemaFolder: string; + dbSchema: string; + privateDBSchema: string; + mode: string; + readOnly: boolean; + webhooks: WebhookConfig[]; + categoryList: string[]; + getTopUsersCacheTimeMinutes: number; + maxNumberOfActiveWarnings: number; + hoursAfterWarningExpires: number; + rateLimit: { + vote: RateLimitConfig; + view: RateLimitConfig; + }; + mysql?: any; + privateMysql?: any; + minimumPrefix?: string; + maximumPrefix?: string; + redis?: redis.ClientOpts; +} + +export interface WebhookConfig { + url: string; + key: string; + scopes: string[]; +} + +export interface RateLimitConfig { + windowMs: number; + max: number; + message: string; + statusCode: number; +} diff --git a/src/utils/createMemoryCache.js b/src/utils/createMemoryCache.js deleted file mode 100644 index 5ab781c..0000000 --- a/src/utils/createMemoryCache.js +++ /dev/null @@ -1,44 +0,0 @@ -module.exports = function createMemoryCache(memoryFn, cacheTimeMs) { - if (isNaN(cacheTimeMs)) cacheTimeMs = 0; - - // holds the promise results - const cache = new Map(); - // holds the promises that are not fulfilled - const promiseMemory = new Map(); - return (...args) => { - // create cacheKey by joining arguments as string - const cacheKey = args.join('.'); - // check if promising is already running - if (promiseMemory.has(cacheKey)) { - return promiseMemory.get(cacheKey); - } - else { - // check if result is in cache - if (cache.has(cacheKey)) { - const cacheItem = cache.get(cacheKey); - const now = Date.now(); - // check if cache is valid - if (!(cacheItem.cacheTime + cacheTimeMs < now)) { - return Promise.resolve(cacheItem.result); - } - } - // create new promise - const promise = new Promise(async (resolve, reject) => { - resolve((await memoryFn(...args))); - }); - // store promise reference until fulfilled - promiseMemory.set(cacheKey, promise); - return promise.then(result => { - // store promise result in cache - cache.set(cacheKey, { - result, - cacheTime: Date.now(), - }); - // remove fulfilled promise from memory - promiseMemory.delete(cacheKey); - // return promise result - return result; - }); - } - }; -}; diff --git a/src/utils/createMemoryCache.ts b/src/utils/createMemoryCache.ts new file mode 100644 index 0000000..ce26b00 --- /dev/null +++ b/src/utils/createMemoryCache.ts @@ -0,0 +1,43 @@ +export function createMemoryCache(memoryFn: (...args: any[]) => void, cacheTimeMs: number) { + if (isNaN(cacheTimeMs)) cacheTimeMs = 0; + + // holds the promise results + const cache = new Map(); + // holds the promises that are not fulfilled + const promiseMemory = new Map(); + return (...args: any[]) => { + // create cacheKey by joining arguments as string + const cacheKey = args.join('.'); + // check if promising is already running + if (promiseMemory.has(cacheKey)) { + return promiseMemory.get(cacheKey); + } else { + // check if result is in cache + if (cache.has(cacheKey)) { + const cacheItem = cache.get(cacheKey); + const now = Date.now(); + // check if cache is valid + if (!(cacheItem.cacheTime + cacheTimeMs < now)) { + return Promise.resolve(cacheItem.result); + } + } + // create new promise + const promise = new Promise(async (resolve) => { + resolve((await memoryFn(...args))); + }); + // store promise reference until fulfilled + promiseMemory.set(cacheKey, promise); + return promise.then(result => { + // store promise result in cache + cache.set(cacheKey, { + result, + cacheTime: Date.now(), + }); + // remove fulfilled promise from memory + promiseMemory.delete(cacheKey); + // return promise result + return result; + }); + } + }; +} diff --git a/src/utils/getFormattedTime.js b/src/utils/getFormattedTime.js deleted file mode 100644 index 0d86271..0000000 --- a/src/utils/getFormattedTime.js +++ /dev/null @@ -1,14 +0,0 @@ -//converts time in seconds to minutes:seconds -module.exports = function getFormattedTime(totalSeconds) { - let minutes = Math.floor(totalSeconds / 60); - let seconds = totalSeconds - minutes * 60; - let secondsDisplay = seconds.toFixed(3); - if (seconds < 10) { - //add a zero - secondsDisplay = "0" + secondsDisplay; - } - - let formatted = minutes+ ":" + secondsDisplay; - - return formatted; -} \ No newline at end of file diff --git a/src/utils/getFormattedTime.ts b/src/utils/getFormattedTime.ts new file mode 100644 index 0000000..afdc020 --- /dev/null +++ b/src/utils/getFormattedTime.ts @@ -0,0 +1,14 @@ +/** + * Converts time in seconds to minutes:seconds + */ +export function getFormattedTime(totalSeconds: number) { + let minutes = Math.floor(totalSeconds / 60); + let seconds = totalSeconds - minutes * 60; + let secondsDisplay = seconds.toFixed(3); + if (seconds < 10) { + //add a zero + secondsDisplay = '0' + secondsDisplay; + } + + return minutes + ':' + secondsDisplay; +} diff --git a/src/utils/getHash.js b/src/utils/getHash.js deleted file mode 100644 index d5b3130..0000000 --- a/src/utils/getHash.js +++ /dev/null @@ -1,12 +0,0 @@ -var crypto = require('crypto'); - -module.exports = function (value, times=5000) { - if (times <= 0) return ""; - - for (let i = 0; i < times; i++) { - let hashCreator = crypto.createHash('sha256'); - value = hashCreator.update(value).digest('hex'); - } - - return value; -} diff --git a/src/utils/getHash.ts b/src/utils/getHash.ts new file mode 100644 index 0000000..b9d770d --- /dev/null +++ b/src/utils/getHash.ts @@ -0,0 +1,12 @@ +import crypto from 'crypto'; + +export function getHash(value: string, times = 5000) { + if (times <= 0) return ""; + + for (let i = 0; i < times; i++) { + let hashCreator = crypto.createHash('sha256'); + value = hashCreator.update(value).digest('hex'); + } + + return value; +} diff --git a/src/utils/getIP.js b/src/utils/getIP.js deleted file mode 100644 index a26130b..0000000 --- a/src/utils/getIP.js +++ /dev/null @@ -1,16 +0,0 @@ -var config = require('../config.js'); - -module.exports = function getIP(req) { - if (config.behindProxy === true || config.behindProxy === "true") config.behindProxy = "X-Forwarded-For"; - - switch (config.behindProxy) { - case "X-Forwarded-For": - return req.headers['x-forwarded-for']; - case "Cloudflare": - return req.headers['cf-connecting-ip']; - case "X-Real-IP": - return req.headers['x-real-ip']; - default: - return req.connection.remoteAddress; - } -} diff --git a/src/utils/getIP.ts b/src/utils/getIP.ts new file mode 100644 index 0000000..c6bee7f --- /dev/null +++ b/src/utils/getIP.ts @@ -0,0 +1,19 @@ +import {config} from '../config'; +import {Request} from 'express'; + +export function getIP(req: Request): string { + if (config.behindProxy === true || config.behindProxy === "true") { + config.behindProxy = "X-Forwarded-For"; + } + + switch (config.behindProxy as string) { + case "X-Forwarded-For": + return req.headers['x-forwarded-for'] as string; + case "Cloudflare": + return req.headers['cf-connecting-ip'] as string; + case "X-Real-IP": + return req.headers['x-real-ip'] as string; + default: + return req.connection.remoteAddress; + } +} diff --git a/src/utils/getSubmissionUUID.js b/src/utils/getSubmissionUUID.js deleted file mode 100644 index 4142be1..0000000 --- a/src/utils/getSubmissionUUID.js +++ /dev/null @@ -1,7 +0,0 @@ -const getHash = require('./getHash.js'); - -module.exports = function getSubmissionUUID(videoID, category, userID, - startTime, endTime) { - return getHash('v2-categories' + videoID + startTime + endTime + category + - userID, 1); -}; diff --git a/src/utils/getSubmissionUUID.ts b/src/utils/getSubmissionUUID.ts new file mode 100644 index 0000000..ff72011 --- /dev/null +++ b/src/utils/getSubmissionUUID.ts @@ -0,0 +1,5 @@ +import {getHash} from './getHash'; + +export function getSubmissionUUID(videoID: string, category: string, userID: string, startTime: number, endTime: number) { + return getHash('v2-categories' + videoID + startTime + endTime + category + userID, 1); +} diff --git a/src/utils/hashPrefixTester.js b/src/utils/hashPrefixTester.ts similarity index 73% rename from src/utils/hashPrefixTester.js rename to src/utils/hashPrefixTester.ts index f5aec61..d9bb0c6 100644 --- a/src/utils/hashPrefixTester.js +++ b/src/utils/hashPrefixTester.ts @@ -1,10 +1,10 @@ -const config = require('../config.js'); +import {config} from '../config'; const minimumPrefix = config.minimumPrefix || '3'; const maximumPrefix = config.maximumPrefix || '32'; // Half the hash. const prefixChecker = new RegExp('^[\\da-f]{' + minimumPrefix + ',' + maximumPrefix + '}$', 'i'); -module.exports = (prefix) => { +export function hashPrefixTester(prefix: string): boolean { return prefixChecker.test(prefix); -}; \ No newline at end of file +} diff --git a/src/utils/isUserTrustworthy.js b/src/utils/isUserTrustworthy.js deleted file mode 100644 index 4e46562..0000000 --- a/src/utils/isUserTrustworthy.js +++ /dev/null @@ -1,20 +0,0 @@ -var databases = require('../databases/databases.js'); -var db = databases.db; - -//returns true if the user is considered trustworthy -//this happens after a user has made 5 submissions and has less than 60% downvoted submissions - -module.exports = async (userID) => { - //check to see if this user how many submissions this user has submitted - let totalSubmissionsRow = db.prepare('get', "SELECT count(*) as totalSubmissions, sum(votes) as voteSum FROM sponsorTimes WHERE userID = ?", [userID]); - - if (totalSubmissionsRow.totalSubmissions > 5) { - //check if they have a high downvote ratio - let downvotedSubmissionsRow = db.prepare('get', "SELECT count(*) as downvotedSubmissions FROM sponsorTimes WHERE userID = ? AND (votes < 0 OR shadowHidden > 0)", [userID]); - - return (downvotedSubmissionsRow.downvotedSubmissions / totalSubmissionsRow.totalSubmissions) < 0.6 || - (totalSubmissionsRow.voteSum > downvotedSubmissionsRow.downvotedSubmissions); - } - - return true; -} \ No newline at end of file diff --git a/src/utils/isUserTrustworthy.ts b/src/utils/isUserTrustworthy.ts new file mode 100644 index 0000000..884ea2e --- /dev/null +++ b/src/utils/isUserTrustworthy.ts @@ -0,0 +1,20 @@ +import {db} from '../databases/databases'; + +/** + * Returns true if the user is considered trustworthy. This happens after a user has made 5 submissions and has less than 60% downvoted submissions + * @param userID + */ +export async function isUserTrustworthy(userID: string): Promise { + //check to see if this user how many submissions this user has submitted + const totalSubmissionsRow = db.prepare('get', "SELECT count(*) as totalSubmissions, sum(votes) as voteSum FROM sponsorTimes WHERE userID = ?", [userID]); + + if (totalSubmissionsRow.totalSubmissions > 5) { + //check if they have a high downvote ratio + const downvotedSubmissionsRow = db.prepare('get', "SELECT count(*) as downvotedSubmissions FROM sponsorTimes WHERE userID = ? AND (votes < 0 OR shadowHidden > 0)", [userID]); + + return (downvotedSubmissionsRow.downvotedSubmissions / totalSubmissionsRow.totalSubmissions) < 0.6 || + (totalSubmissionsRow.voteSum > downvotedSubmissionsRow.downvotedSubmissions); + } + + return true; +} diff --git a/src/utils/isUserVIP.js b/src/utils/isUserVIP.ts similarity index 52% rename from src/utils/isUserVIP.js rename to src/utils/isUserVIP.ts index eeb25dc..14c2f33 100644 --- a/src/utils/isUserVIP.js +++ b/src/utils/isUserVIP.ts @@ -1,7 +1,6 @@ -const databases = require('../databases/databases.js'); -const db = databases.db; +import {db} from '../databases/databases'; -module.exports = (userID) => { +export function isUserVIP(userID: string): boolean { return db.prepare('get', "SELECT count(*) as userCount FROM vipUsers WHERE userID = ?", [userID]).userCount > 0; } diff --git a/src/utils/logger.js b/src/utils/logger.js deleted file mode 100644 index 66ebb03..0000000 --- a/src/utils/logger.js +++ /dev/null @@ -1,70 +0,0 @@ -const config = require('../config.js'); - -const levels = { - ERROR: "ERROR", - WARN: "WARN", - INFO: "INFO", - DEBUG: "DEBUG" -}; - -const colors = { - Reset: "\x1b[0m", - Bright: "\x1b[1m", - Dim: "\x1b[2m", - Underscore: "\x1b[4m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - Hidden: "\x1b[8m", - - FgBlack: "\x1b[30m", - FgRed: "\x1b[31m", - FgGreen: "\x1b[32m", - FgYellow: "\x1b[33m", - FgBlue: "\x1b[34m", - FgMagenta: "\x1b[35m", - FgCyan: "\x1b[36m", - FgWhite: "\x1b[37m", - - BgBlack: "\x1b[40m", - BgRed: "\x1b[41m", - BgGreen: "\x1b[42m", - BgYellow: "\x1b[43m", - BgBlue: "\x1b[44m", - BgMagenta: "\x1b[45m", - BgCyan: "\x1b[46m", - BgWhite: "\x1b[47m", -} - -const settings = { - ERROR: true, - WARN: true, - INFO: false, - DEBUG: false -}; - -if (config.mode === 'development') { - settings.INFO = true; - settings.DEBUG = true; -} else if (config.mode === 'test') { - settings.WARN = false; -} - -function log(level, string) { - if (!!settings[level]) { - let color = colors.Bright; - if (level === levels.ERROR) color = colors.FgRed; - if (level === levels.WARN) color = colors.FgYellow; - - if (level.length === 4) {level = level + " "}; // ensure logs are aligned - console.log(colors.Dim, level + " " + new Date().toISOString() + ": ", color, string, colors.Reset); - } -} - -module.exports = { - levels, - log, - error: (string) => {log(levels.ERROR, string)}, - warn: (string) => {log(levels.WARN, string)}, - info: (string) => {log(levels.INFO, string)}, - debug: (string) => {log(levels.DEBUG, string)}, -}; \ No newline at end of file diff --git a/src/utils/logger.ts b/src/utils/logger.ts new file mode 100644 index 0000000..bc29588 --- /dev/null +++ b/src/utils/logger.ts @@ -0,0 +1,89 @@ +import {config} from '../config'; + +const enum LogLevel { + ERROR = "ERROR", + WARN = "WARN", + INFO = "INFO", + DEBUG = "DEBUG" +} + +const colors = { + Reset: "\x1b[0m", + Bright: "\x1b[1m", + Dim: "\x1b[2m", + Underscore: "\x1b[4m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + Hidden: "\x1b[8m", + + FgBlack: "\x1b[30m", + FgRed: "\x1b[31m", + FgGreen: "\x1b[32m", + FgYellow: "\x1b[33m", + FgBlue: "\x1b[34m", + FgMagenta: "\x1b[35m", + FgCyan: "\x1b[36m", + FgWhite: "\x1b[37m", + + BgBlack: "\x1b[40m", + BgRed: "\x1b[41m", + BgGreen: "\x1b[42m", + BgYellow: "\x1b[43m", + BgBlue: "\x1b[44m", + BgMagenta: "\x1b[45m", + BgCyan: "\x1b[46m", + BgWhite: "\x1b[47m", +}; + + +class Logger { + private _settings = { + ERROR: true, + WARN: true, + INFO: false, + DEBUG: false, + }; + + constructor() { + if (config.mode === 'development') { + this._settings.INFO = true; + this._settings.DEBUG = true; + } else if (config.mode === 'test') { + this._settings.WARN = false; + } + } + + error(str: string) { + this.log(LogLevel.ERROR, str); + } + warn(str: string) { + this.log(LogLevel.WARN, str); + } + info(str: string) { + this.log(LogLevel.INFO, str); + } + debug(str: string) { + this.log(LogLevel.DEBUG, str); + } + + private log(level: LogLevel, str: string) { + if (!this._settings[level]) { + return; + } + + let color = colors.Bright; + if (level === LogLevel.ERROR) color = colors.FgRed; + if (level === LogLevel.WARN) color = colors.FgYellow; + + let levelStr = level.toString(); + if (levelStr.length === 4) { + levelStr += " "; // ensure logs are aligned + } + console.log(colors.Dim, `${levelStr} ${new Date().toISOString()}: `, color, str, colors.Reset); + } +} + +const loggerInstance = new Logger(); +export { + loggerInstance as Logger +} diff --git a/src/utils/redis.js b/src/utils/redis.js deleted file mode 100644 index b278b6f..0000000 --- a/src/utils/redis.js +++ /dev/null @@ -1,18 +0,0 @@ -const config = require('../config.js'); -const logger = require('./logger.js'); - -if (config.redis) { - const redis = require('redis'); - logger.info('Connected to redis'); - const client = redis.createClient(config.redis); - module.exports = client; -} else { - module.exports = { - get: (key, callback) => { - callback(false); - }, - set: (key, value, callback) => { - callback(false); - } - }; -} diff --git a/src/utils/redis.ts b/src/utils/redis.ts new file mode 100644 index 0000000..a310e69 --- /dev/null +++ b/src/utils/redis.ts @@ -0,0 +1,19 @@ +import {config} from '../config'; +import {Logger} from './logger'; +import redis, {Callback} from 'redis'; + +let get, set; +if (config.redis) { + Logger.info('Connected to redis'); + const client = redis.createClient(config.redis); + get = client.get; + set = client.set; +} else { + get = (key: string, callback?: Callback) => callback(null, undefined); + set = (key: string, value: string, callback?: Callback) => callback(null, undefined); +} + +export { + get, + set, +}; diff --git a/src/utils/webhookUtils.js b/src/utils/webhookUtils.js deleted file mode 100644 index 9b1e0e7..0000000 --- a/src/utils/webhookUtils.js +++ /dev/null @@ -1,52 +0,0 @@ -const config = require('../config.js'); -const logger = require('../utils/logger.js'); -const request = require('request'); - -function getVoteAuthorRaw(submissionCount, isVIP, isOwnSubmission) { - if (isOwnSubmission) { - return "self"; - } else if (isVIP) { - return "vip"; - } else if (submissionCount === 0) { - return "new"; - } else { - return "other"; - }; -}; - -function getVoteAuthor(submissionCount, isVIP, isOwnSubmission) { - if (submissionCount === 0) { - return "Report by New User"; - } else if (isOwnSubmission) { - return "Report by Submitter"; - } else if (isVIP) { - return "Report by VIP User"; - } - - return ""; -} - -function dispatchEvent(scope, data) { - let webhooks = config.webhooks; - if (webhooks === undefined || webhooks.length === 0) return; - logger.debug("Dispatching webhooks"); - webhooks.forEach(webhook => { - let webhookURL = webhook.url; - let authKey = webhook.key; - let scopes = webhook.scopes || []; - if (!scopes.includes(scope.toLowerCase())) return; - request.post(webhookURL, {json: data, headers: { - "Authorization": authKey, - "Event-Type": scope // Maybe change this in the future? - }}).on('error', (e) => { - logger.warn('Couldn\'t send webhook to ' + webhook.url); - logger.warn(e); - }); - }); -} - -module.exports = { - getVoteAuthorRaw, - getVoteAuthor, - dispatchEvent -} \ No newline at end of file diff --git a/src/utils/webhookUtils.ts b/src/utils/webhookUtils.ts new file mode 100644 index 0000000..fdd215e --- /dev/null +++ b/src/utils/webhookUtils.ts @@ -0,0 +1,56 @@ +import {config} from '../config'; +import {Logger} from '../utils/logger'; +import request from 'request'; + +function getVoteAuthorRaw(submissionCount: number, isVIP: boolean, isOwnSubmission: boolean): string { + if (isOwnSubmission) { + return "self"; + } else if (isVIP) { + return "vip"; + } else if (submissionCount === 0) { + return "new"; + } else { + return "other"; + } +} + +function getVoteAuthor(submissionCount: number, isVIP: boolean, isOwnSubmission: boolean): string { + if (submissionCount === 0) { + return "Report by New User"; + } else if (isOwnSubmission) { + return "Report by Submitter"; + } else if (isVIP) { + return "Report by VIP User"; + } + + return ""; +} + +function dispatchEvent(scope: string, data: any): void { + let webhooks = config.webhooks; + if (webhooks === undefined || webhooks.length === 0) return; + Logger.debug("Dispatching webhooks"); + webhooks.forEach(webhook => { + let webhookURL = webhook.url; + let authKey = webhook.key; + let scopes = webhook.scopes || []; + if (!scopes.includes(scope.toLowerCase())) return; + + // TODO TYPESCRIPT deprecated + request.post(webhookURL, { + json: data, headers: { + "Authorization": authKey, + "Event-Type": scope, // Maybe change this in the future? + }, + }).on('error', (e) => { + Logger.warn('Couldn\'t send webhook to ' + webhook.url); + Logger.warn(e.message); + }); + }); +} + +export { + getVoteAuthorRaw, + getVoteAuthor, + dispatchEvent, +}; diff --git a/src/utils/youtubeAPI.js b/src/utils/youtubeAPI.js deleted file mode 100644 index 96e718e..0000000 --- a/src/utils/youtubeAPI.js +++ /dev/null @@ -1,62 +0,0 @@ -var config = require('../config.js'); - -// YouTube API -const YouTubeAPI = require("youtube-api"); -const redis = require('./redis.js'); -const logger = require('./logger.js'); - -var exportObject; -// If in test mode, return a mocked youtube object -// otherwise return an authenticated youtube api -if (config.mode === "test") { - exportObject = require("../../test/youtubeMock.js"); -} else { - YouTubeAPI.authenticate({ - type: "key", - key: config.youtubeAPIKey - }); - exportObject = YouTubeAPI; - - // YouTubeAPI.videos.list wrapper with cacheing - exportObject.listVideos = (videoID, callback) => { - let part = 'contentDetails,snippet'; - if (videoID.length !== 11 || videoID.includes(".")) { - callback("Invalid video ID"); - return; - } - - let redisKey = "youtube.video." + videoID; - redis.get(redisKey, (getErr, result) => { - if (getErr || !result) { - logger.debug("redis: no cache for video information: " + videoID); - YouTubeAPI.videos.list({ - part, - id: videoID - }, (ytErr, data) => { - if (!ytErr) { - // Only set cache if data returned - if (data.items.length > 0) { - redis.set(redisKey, JSON.stringify(data), (setErr) => { - if(setErr) { - logger.warn(setErr); - } else { - logger.debug("redis: video information cache set for: " + videoID); - } - callback(false, data); // don't fail - }); - } else { - callback(false, data); // don't fail - } - } else { - callback(ytErr, data) - } - }); - } else { - logger.debug("redis: fetched video information from cache: " + videoID); - callback(getErr, JSON.parse(result)); - } - }); - }; -} - -module.exports = exportObject; \ No newline at end of file diff --git a/src/utils/youtubeApi.ts b/src/utils/youtubeApi.ts new file mode 100644 index 0000000..af71213 --- /dev/null +++ b/src/utils/youtubeApi.ts @@ -0,0 +1,69 @@ +import {config} from '../config'; +import {Logger} from './logger'; +import * as redis from './redis'; +// @ts-ignore +import YouTubeAPI from 'youtube-api'; + +import {YouTubeAPI as youtubeApiTest} from '../../test/youtubeMock'; + +let _youtubeApi: { + listVideos: (videoID: string, callback: (err: string | boolean, data: any) => void) => void +}; +// If in test mode, return a mocked youtube object +// otherwise return an authenticated youtube api +if (config.mode === "test") { + _youtubeApi = youtubeApiTest; +} +else { + _youtubeApi = YouTubeAPI; + + YouTubeAPI.authenticate({ + type: "key", + key: config.youtubeAPIKey, + }); + + // YouTubeAPI.videos.list wrapper with cacheing + _youtubeApi.listVideos = (videoID: string, callback: (err: string | boolean, data: any) => void) => { + const part = 'contentDetails,snippet'; + if (videoID.length !== 11 || videoID.includes(".")) { + callback("Invalid video ID", undefined); + return; + } + + const redisKey = "youtube.video." + videoID; + redis.get(redisKey, (getErr: string, result: string) => { + if (getErr || !result) { + Logger.debug("redis: no cache for video information: " + videoID); + YouTubeAPI.videos.list({ + part, + id: videoID, + }, (ytErr: boolean | string, data: any) => { + if (!ytErr) { + // Only set cache if data returned + if (data.items.length > 0) { + redis.set(redisKey, JSON.stringify(data), (setErr: string) => { + if (setErr) { + Logger.warn(setErr); + } else { + Logger.debug("redis: video information cache set for: " + videoID); + } + callback(false, data); // don't fail + }); + } else { + callback(false, data); // don't fail + } + } else { + callback(ytErr, data); + } + }); + } else { + Logger.debug("redis: fetched video information from cache: " + videoID); + callback(getErr, JSON.parse(result)); + } + }); + }; +} + +export { + _youtubeApi as YouTubeAPI +} diff --git a/test.js b/test.js deleted file mode 100644 index f4e54ff..0000000 --- a/test.js +++ /dev/null @@ -1,41 +0,0 @@ -var Mocha = require('mocha'), - fs = require('fs'), - path = require('path'); - -var config = require('./src/config.js'); -// delete old test database -if (fs.existsSync(config.db)) fs.unlinkSync(config.db); -if (fs.existsSync(config.privateDB)) fs.unlinkSync(config.privateDB); - -var createServer = require('./src/app.js'); -var createMockServer = require('./test/mocks.js'); -const logger = require('./src/utils/logger.js'); - -// Instantiate a Mocha instance. -var mocha = new Mocha(); - -var testDir = './test/cases' - -// Add each .js file to the mocha instance -fs.readdirSync(testDir).filter(function(file) { - // Only keep the .js files - return file.substr(-3) === '.js'; - -}).forEach(function(file) { - mocha.addFile( - path.join(testDir, file) - ); -}); - -var mockServer = createMockServer(() => { - logger.info("Started mock HTTP Server"); - var server = createServer(() => { - logger.info("Started main HTTP server"); - // Run the tests. - mocha.run((failures) => { - mockServer.close(); - server.close(); - process.exitCode = failures ? 1 : 0; // exit with non-zero status if there were failures - }); - }); -}); diff --git a/test.ts b/test.ts new file mode 100644 index 0000000..f995320 --- /dev/null +++ b/test.ts @@ -0,0 +1,44 @@ +import Mocha from 'mocha'; +import fs from 'fs'; +import path from 'path'; +import {config} from './src/config'; +import {createServer} from './src/app'; +import {createMockServer} from './test/mocks'; +import {Logger} from './src/utils/logger'; +import {initDb} from './src/databases/databases'; + +// delete old test database +if (fs.existsSync(config.db)) fs.unlinkSync(config.db) +if (fs.existsSync(config.privateDB)) fs.unlinkSync(config.privateDB); + +initDb(); + +// Instantiate a Mocha instance. +const mocha = new Mocha(); + +const testDir = './test/cases'; + +// Add each .ts file to the mocha instance +fs.readdirSync(testDir) + .filter(function(file) { + // Only keep the .ts files + return file.substr(-3) === '.ts'; + }) + .forEach(function(file) { + mocha.addFile( + path.join(testDir, file) + ); + }); + +const mockServer = createMockServer(() => { + Logger.info("Started mock HTTP Server"); + const server = createServer(() => { + Logger.info("Started main HTTP server"); + // Run the tests. + mocha.run((failures) => { + mockServer.close(); + server.close(); + process.exitCode = failures ? 1 : 0; // exit with non-zero status if there were failures + }); + }); +}); diff --git a/test/cases/dbUpgrade.js b/test/cases/dbUpgrade.js deleted file mode 100644 index 8228d27..0000000 --- a/test/cases/dbUpgrade.js +++ /dev/null @@ -1,12 +0,0 @@ -const databases = require('../../src/databases/databases.js'); -const db = databases.db; -const privateDB = databases.privateDB; - -describe('dbUpgrade', () => { - it('Should update the database version when starting the application', (done) => { - let dbVersion = db.prepare('get', 'SELECT key, value FROM config where key = ?', ['version']).value; - let privateVersion = privateDB.prepare('get', 'SELECT key, value FROM config where key = ?', ['version']).value; - if (dbVersion >= 1 && privateVersion >= 1) done(); - else done('Versions are not at least 1. db is ' + dbVersion + ', private is ' + privateVersion); - }); -}); \ No newline at end of file diff --git a/test/cases/dbUpgrade.ts b/test/cases/dbUpgrade.ts new file mode 100644 index 0000000..03fd6c5 --- /dev/null +++ b/test/cases/dbUpgrade.ts @@ -0,0 +1,11 @@ +import {db, privateDB} from '../../src/databases/databases'; +import {Done} from '../utils'; + +describe('dbUpgrade', () => { + it('Should update the database version when starting the application', (done: Done) => { + let dbVersion = db.prepare('get', 'SELECT key, value FROM config where key = ?', ['version']).value; + let privateVersion = privateDB.prepare('get', 'SELECT key, value FROM config where key = ?', ['version']).value; + if (dbVersion >= 1 && privateVersion >= 1) done(); + else done('Versions are not at least 1. db is ' + dbVersion + ', private is ' + privateVersion); + }); +}); diff --git a/test/cases/getHash.js b/test/cases/getHash.js deleted file mode 100644 index 896076d..0000000 --- a/test/cases/getHash.js +++ /dev/null @@ -1,33 +0,0 @@ -var getHash = require('../../src/utils/getHash.js'); - -var assert = require('assert'); -describe('getHash', () => { - it('Should not output the input string', () => { - assert(getHash("test") !== "test"); - assert(getHash("test", -1) !== "test"); - assert(getHash("test", 0) !== "test"); - assert(getHash("test", null) !== "test"); - }); - - it('Should return a hashed value', () => { - assert.equal(getHash("test"), "2f327ef967ade1ebf4319163f7debbda9cc17bb0c8c834b00b30ca1cf1c256ee"); - }); - - it ('Should be able to output the same has the DB upgrade script will output', () => { - assert.equal(getHash("vid", 1), "1ff838dc6ca9680d88455341118157d59a055fe6d0e3870f9c002847bebe4663"); - }); - - it ('Should take a variable number of passes', () => { - assert.equal(getHash("test", 1), "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"); - assert.equal(getHash("test", 2), "7b3d979ca8330a94fa7e9e1b466d8b99e0bcdea1ec90596c0dcc8d7ef6b4300c"); - assert.equal(getHash("test", 3), "5b24f7aa99f1e1da5698a4f91ae0f4b45651a1b625c61ed669dd25ff5b937972"); - }); - - it ('Should default to 5000 passes', () => { - assert.equal(getHash("test"), getHash("test", 5000)); - }); - - it ('Should not take a negative number of passes', () => { - assert.equal(getHash("test", -1), ""); - }); -}); \ No newline at end of file diff --git a/test/cases/getHash.ts b/test/cases/getHash.ts new file mode 100644 index 0000000..ab65763 --- /dev/null +++ b/test/cases/getHash.ts @@ -0,0 +1,33 @@ +import {getHash} from '../../src/utils/getHash'; +import assert from 'assert'; + +describe('getHash', () => { + it('Should not output the input string', () => { + assert(getHash("test") !== "test"); + assert(getHash("test", -1) !== "test"); + assert(getHash("test", 0) !== "test"); + assert(getHash("test", null) !== "test"); + }); + + it('Should return a hashed value', () => { + assert.strictEqual(getHash("test"), "2f327ef967ade1ebf4319163f7debbda9cc17bb0c8c834b00b30ca1cf1c256ee"); + }); + + it('Should be able to output the same has the DB upgrade script will output', () => { + assert.strictEqual(getHash("vid", 1), "1ff838dc6ca9680d88455341118157d59a055fe6d0e3870f9c002847bebe4663"); + }); + + it('Should take a variable number of passes', () => { + assert.strictEqual(getHash("test", 1), "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"); + assert.strictEqual(getHash("test", 2), "7b3d979ca8330a94fa7e9e1b466d8b99e0bcdea1ec90596c0dcc8d7ef6b4300c"); + assert.strictEqual(getHash("test", 3), "5b24f7aa99f1e1da5698a4f91ae0f4b45651a1b625c61ed669dd25ff5b937972"); + }); + + it('Should default to 5000 passes', () => { + assert.strictEqual(getHash("test"), getHash("test", 5000)); + }); + + it('Should not take a negative number of passes', () => { + assert.strictEqual(getHash("test", -1), ""); + }); +}); diff --git a/test/cases/getIsUserVIP.js b/test/cases/getIsUserVIP.js deleted file mode 100644 index 1bbfbfe..0000000 --- a/test/cases/getIsUserVIP.js +++ /dev/null @@ -1,57 +0,0 @@ -var request = require('request'); -var utils = require('../utils.js'); -var db = require('../../src/databases/databases.js').db; -var getHash = require('../../src/utils/getHash.js'); - -describe('getIsUserVIP', () => { - before(() => { - db.exec("INSERT INTO vipUsers (userID) VALUES ('" + getHash("supertestman") + "')"); - }); - - it('Should be able to get a 200', (done) => { - request.get(utils.getbaseURL() - + "/api/isUserVIP?userID=supertestman", null, - (err, res, body) => { - if (err) done("couldn't call endpoint"); - else if (res.statusCode !== 200) done("non 200: " + res.statusCode); - else done(); // pass - }); - }); - - - it('Should get a 400 if no userID', (done) => { - request.get(utils.getbaseURL() - + "/api/isUserVIP", null, - (err, res, body) => { - if (err) done("couldn't call endpoint"); - else if (res.statusCode !== 400) done("non 400: " + res.statusCode); - else done(); // pass - }); - }); - - it('Should say a VIP is a VIP', (done) => { - request.get(utils.getbaseURL() - + "/api/isUserVIP?userID=supertestman", null, - (err, res, body) => { - if (err) done("couldn't call endpoint"); - else if (res.statusCode !== 200) done("non 200: " + res.statusCode); - else { - if (JSON.parse(body).vip === true) done(); // pass - else done("Result was non-vip when should have been vip"); - } - }); - }); - - it('Should say a normal user is not a VIP', (done) => { - request.get(utils.getbaseURL() - + "/api/isUserVIP?userID=regulartestman", null, - (err, res, body) => { - if (err) done("couldn't call endpoint"); - else if (res.statusCode !== 200) done("non 200: " + res.statusCode); - else { - if (JSON.parse(body).vip === false) done(); // pass - else done("Result was vip when should have been non-vip"); - } - }); - }); -}); \ No newline at end of file diff --git a/test/cases/getIsUserVIP.ts b/test/cases/getIsUserVIP.ts new file mode 100644 index 0000000..8c48c4e --- /dev/null +++ b/test/cases/getIsUserVIP.ts @@ -0,0 +1,57 @@ +import request from 'request'; +import {getbaseURL, Done} from '../utils'; +import {db} from '../../src/databases/databases'; +import {getHash} from '../../src/utils/getHash'; + +describe('getIsUserVIP', () => { + before(() => { + db.exec("INSERT INTO vipUsers (userID) VALUES ('" + getHash("supertestman") + "')"); + }); + + it('Should be able to get a 200', (done: Done) => { + request.get(getbaseURL() + + "/api/isUserVIP?userID=supertestman", null, + (err, res) => { + if (err) done("couldn't call endpoint"); + else if (res.statusCode !== 200) done("non 200: " + res.statusCode); + else done(); // pass + }); + }); + + + it('Should get a 400 if no userID', (done: Done) => { + request.get(getbaseURL() + + "/api/isUserVIP", null, + (err, res) => { + if (err) done("couldn't call endpoint"); + else if (res.statusCode !== 400) done("non 400: " + res.statusCode); + else done(); // pass + }); + }); + + it('Should say a VIP is a VIP', (done: Done) => { + request.get(getbaseURL() + + "/api/isUserVIP?userID=supertestman", null, + (err, res, body) => { + if (err) done("couldn't call endpoint"); + else if (res.statusCode !== 200) done("non 200: " + res.statusCode); + else { + if (JSON.parse(body).vip === true) done(); // pass + else done("Result was non-vip when should have been vip"); + } + }); + }); + + it('Should say a normal user is not a VIP', (done: Done) => { + request.get(getbaseURL() + + "/api/isUserVIP?userID=regulartestman", null, + (err, res, body) => { + if (err) done("couldn't call endpoint"); + else if (res.statusCode !== 200) done("non 200: " + res.statusCode); + else { + if (JSON.parse(body).vip === false) done(); // pass + else done("Result was vip when should have been non-vip"); + } + }); + }); +}); diff --git a/test/cases/getSavedTimeForUser.js b/test/cases/getSavedTimeForUser.js deleted file mode 100644 index 242204d..0000000 --- a/test/cases/getSavedTimeForUser.js +++ /dev/null @@ -1,21 +0,0 @@ -var request = require('request'); -var utils = require('../utils.js'); -var db = require('../../src/databases/databases.js').db; -var getHash = require('../../src/utils/getHash.js'); - -describe('getSavedTimeForUser', () => { - before(() => { - let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden, hashedVideoID) VALUES"; - db.exec(startOfQuery + "('getSavedTimeForUser', 1, 11, 2, 'abc1239999', '" + getHash("testman") + "', 0, 50, 'sponsor', 0, '" + getHash('getSavedTimeForUser', 0) + "')"); - }); - - it('Should be able to get a 200', (done) => { - request.get(utils.getbaseURL() - + "/api/getSavedTimeForUser?userID=testman", null, - (err, res, body) => { - if (err) done("couldn't call endpoint"); - else if (res.statusCode !== 200) done("non 200"); - else done(); // pass - }); - }); -}); \ No newline at end of file diff --git a/test/cases/getSavedTimeForUser.ts b/test/cases/getSavedTimeForUser.ts new file mode 100644 index 0000000..a7627d4 --- /dev/null +++ b/test/cases/getSavedTimeForUser.ts @@ -0,0 +1,21 @@ +import request from 'request'; +import {Done, getbaseURL} from '../utils'; +import {db} from '../../src/databases/databases'; +import {getHash} from '../../src/utils/getHash'; + +describe('getSavedTimeForUser', () => { + before(() => { + let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden, hashedVideoID) VALUES"; + db.exec(startOfQuery + "('getSavedTimeForUser', 1, 11, 2, 'abc1239999', '" + getHash("testman") + "', 0, 50, 'sponsor', 0, '" + getHash('getSavedTimeForUser', 0) + "')"); + }); + + it('Should be able to get a 200', (done: Done) => { + request.get(getbaseURL() + + "/api/getSavedTimeForUser?userID=testman", null, + (err, res) => { + if (err) done("couldn't call endpoint"); + else if (res.statusCode !== 200) done("non 200"); + else done(); // pass + }); + }); +}); diff --git a/test/cases/getSegmentsByHash.js b/test/cases/getSegmentsByHash.js deleted file mode 100644 index ed4f819..0000000 --- a/test/cases/getSegmentsByHash.js +++ /dev/null @@ -1,190 +0,0 @@ -var request = require('request'); -var db = require('../../src/databases/databases.js').db; -var utils = require('../utils.js'); -var getHash = require('../../src/utils/getHash.js'); - -describe('getSegmentsByHash', () => { - before(() => { - let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden, hashedVideoID) VALUES"; - db.exec(startOfQuery + "('getSegmentsByHash-0', 1, 10, 2, 'getSegmentsByHash-0-0', 'testman', 0, 50, 'sponsor', 0, '" + getHash('getSegmentsByHash-0', 1) + "')"); // hash = fdaff4dee1043451faa7398324fb63d8618ebcd11bddfe0491c488db12c6c910 - db.exec(startOfQuery + "('getSegmentsByHash-0', 20, 30, 2, 'getSegmentsByHash-0-1', 'testman', 100, 150, 'intro', 0, '" + getHash('getSegmentsByHash-0', 1) + "')"); // hash = fdaff4dee1043451faa7398324fb63d8618ebcd11bddfe0491c488db12c6c910 - db.exec(startOfQuery + "('getSegmentsByHash-noMatchHash', 40, 50, 2, 'getSegmentsByHash-noMatchHash', 'testman', 0, 50, 'sponsor', 0, 'fdaffnoMatchHash')"); // hash = fdaff4dee1043451faa7398324fb63d8618ebcd11bddfe0491c488db12c6c910 - db.exec(startOfQuery + "('getSegmentsByHash-1', 60, 70, 2, 'getSegmentsByHash-1', 'testman', 0, 50, 'sponsor', 0, '" + getHash('getSegmentsByHash-1', 1) + "')"); // hash = 3272fa85ee0927f6073ef6f07ad5f3146047c1abba794cfa364d65ab9921692b - }); - - it('Should be able to get a 200', (done) => { - request.get(utils.getbaseURL() - + '/api/skipSegments/3272f?categories=["sponsor", "intro"]', null, - (err, res, body) => { - if (err) done("Couldn't call endpoint"); - else if (res.statusCode !== 200) done("non 200 status code, was " + res.statusCode); - else { - done(); - } // pass - }); - }); - - it('Should be able to get a 200 with empty segments for video but no matching categories', (done) => { - request.get(utils.getbaseURL() - + '/api/skipSegments/3272f?categories=["shilling"]', null, - (err, res, body) => { - if (err) done("Couldn't call endpoint"); - else if (res.statusCode !== 200) done("non 200 status code, was " + res.statusCode); - else { - if (JSON.parse(body) && JSON.parse(body).length > 0 && JSON.parse(body)[0].segments.length === 0) { - done(); // pass - } else { - done("response had segments"); - } - } - }); - }); - - it('Should be able to get an empty array if no videos', (done) => { - request.get(utils.getbaseURL() - + '/api/skipSegments/11111?categories=["shilling"]', null, - (err, res, body) => { - if (err) done("Couldn't call endpoint"); - else if (res.statusCode !== 404) done("non 404 status code, was " + res.statusCode); - else { - if (JSON.parse(body).length === 0 && body === '[]') done(); // pass - else done("non empty array returned"); - } - }); - }); - - it('Should return 400 prefix too short', (done) => { - request.get(utils.getbaseURL() - + '/api/skipSegments/11?categories=["shilling"]', null, - (err, res, body) => { - if (err) done("Couldn't call endpoint"); - else if (res.statusCode !== 400) done("non 400 status code, was " + res.statusCode); - else { - done(); // pass - } - }); - }); - - it('Should return 400 prefix too long', (done) => { - let prefix = new Array(50).join('1'); - if (prefix.length <= 32) { // default value, config can change this - done('failed to generate a long enough string for the test ' + prefix.length); - return; - } - - request.get(utils.getbaseURL() - + '/api/skipSegments/'+prefix+'?categories=["shilling"]', null, - (err, res, body) => { - if (err) done("Couldn't call endpoint"); - else if (res.statusCode !== 400) done("non 400 status code, was " + res.statusCode); - else { - done(); // pass - } - }); - }); - - it('Should not return 400 prefix in range', (done) => { - request.get(utils.getbaseURL() - + '/api/skipSegments/11111?categories=["shilling"]', null, - (err, res, body) => { - if (err) done("Couldn't call endpoint"); - else if (res.statusCode === 400) done("prefix length 5 gave 400 " + res.statusCode); - else { - done(); // pass - } - }); - }); - - it('Should return 404 for no hash', (done) => { - request.get(utils.getbaseURL() - + '/api/skipSegments/?categories=["shilling"]', null, - (err, res, body) => { - if (err) done("Couldn't call endpoint"); - else if (res.statusCode !== 404) done("expected 404, got " + res.statusCode); - else { - done(); // pass - } - }); - }); - - it('Should return 500 for bad format categories', (done) => { // should probably be 400 - request.get(utils.getbaseURL() - + '/api/skipSegments/?categories=shilling', null, - (err, res, body) => { - if (err) done("Couldn't call endpoint"); - else if (res.statusCode !== 500) done("expected 500 got " + res.statusCode); - else { - done(); // pass - } - }); - }); - - it('Should be able to get multiple videos', (done) => { - request.get(utils.getbaseURL() - + '/api/skipSegments/fdaf?categories=["sponsor","intro"]', null, - (err, res, body) => { - if (err) done("Couldn't call endpoint"); - else if (res.statusCode !== 200) done("non 200 status code, was " + res.statusCode); - else { - body = JSON.parse(body); - if (body.length !== 2) done("expected 2 video, got " + body.length); - else if (body[0].segments.length !== 2) done("expected 2 segments for first video, got " + body[0].segments.length); - else if (body[1].segments.length !== 1) done("expected 1 segment for second video, got " + body[1].segments.length); - else done(); - } - }); - }); - - it('Should be able to get 200 for no categories (default sponsor)', (done) => { - request.get(utils.getbaseURL() - + '/api/skipSegments/fdaf', null, - (err, res, body) => { - if (err) done("Couldn't call endpoint"); - else if (res.statusCode !== 200) done("non 200 status code, was " + res.statusCode); - else { - body = JSON.parse(body); - if (body.length !== 2) done("expected 2 videos, got " + body.length); - else if (body[0].segments.length !== 1) done("expected 1 segments for first video, got " + body[0].segments.length); - else if (body[1].segments.length !== 1) done("expected 1 segments for second video, got " + body[1].segments.length); - else if (body[0].segments[0].category !== 'sponsor' || body[1].segments[0].category !== 'sponsor') done("both segments are not sponsor"); - else done(); - } - }); - }); - - it('Should be able to post a segment and get it using endpoint', (done) => { - let testID = 'abc123goodVideo'; - request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes", { - json: { - userID: "test", - videoID: testID, - segments: [{ - segment: [13, 17], - category: "sponsor" - }] - } - }, - (err, res, body) => { - if (err) done('(post) ' + err); - else if (res.statusCode === 200) { - request.get(utils.getbaseURL() - + '/api/skipSegments/'+getHash(testID, 1).substring(0,3), null, - (err, res, body) => { - if (err) done("(get) Couldn't call endpoint"); - else if (res.statusCode !== 200) done("(get) non 200 status code, was " + res.statusCode); - else { - body = JSON.parse(body); - if (body.length !== 1) done("(get) expected 1 video, got " + body.length); - else if (body[0].segments.length !== 1) done("(get) expected 1 segments for first video, got " + body[0].segments.length); - else if (body[0].segments[0].category !== 'sponsor') done("(get) segment should be sponsor, was "+body[0].segments[0].category); - else done(); - } - }); - } else { - done("(post) non 200 status code, was " + res.statusCode); - } - } - ); - }); -}); \ No newline at end of file diff --git a/test/cases/getSegmentsByHash.ts b/test/cases/getSegmentsByHash.ts new file mode 100644 index 0000000..00b3718 --- /dev/null +++ b/test/cases/getSegmentsByHash.ts @@ -0,0 +1,192 @@ +import request from 'request'; +import {db} from '../../src/databases/databases'; +import {Done, getbaseURL} from '../utils'; +import {getHash} from '../../src/utils/getHash'; + + + +describe('getSegmentsByHash', () => { + before(() => { + let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden, hashedVideoID) VALUES"; + db.exec(startOfQuery + "('getSegmentsByHash-0', 1, 10, 2, 'getSegmentsByHash-0-0', 'testman', 0, 50, 'sponsor', 0, '" + getHash('getSegmentsByHash-0', 1) + "')"); // hash = fdaff4dee1043451faa7398324fb63d8618ebcd11bddfe0491c488db12c6c910 + db.exec(startOfQuery + "('getSegmentsByHash-0', 20, 30, 2, 'getSegmentsByHash-0-1', 'testman', 100, 150, 'intro', 0, '" + getHash('getSegmentsByHash-0', 1) + "')"); // hash = fdaff4dee1043451faa7398324fb63d8618ebcd11bddfe0491c488db12c6c910 + db.exec(startOfQuery + "('getSegmentsByHash-noMatchHash', 40, 50, 2, 'getSegmentsByHash-noMatchHash', 'testman', 0, 50, 'sponsor', 0, 'fdaffnoMatchHash')"); // hash = fdaff4dee1043451faa7398324fb63d8618ebcd11bddfe0491c488db12c6c910 + db.exec(startOfQuery + "('getSegmentsByHash-1', 60, 70, 2, 'getSegmentsByHash-1', 'testman', 0, 50, 'sponsor', 0, '" + getHash('getSegmentsByHash-1', 1) + "')"); // hash = 3272fa85ee0927f6073ef6f07ad5f3146047c1abba794cfa364d65ab9921692b + }); + + it('Should be able to get a 200', (done: Done) => { + request.get(getbaseURL() + + '/api/skipSegments/3272f?categories=["sponsor", "intro"]', null, + (err, res) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode !== 200) done("non 200 status code, was " + res.statusCode); + else { + done(); + } // pass + }); + }); + + it('Should be able to get a 200 with empty segments for video but no matching categories', (done: Done) => { + request.get(getbaseURL() + + '/api/skipSegments/3272f?categories=["shilling"]', null, + (err, res, body) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode !== 200) done("non 200 status code, was " + res.statusCode); + else { + if (JSON.parse(body) && JSON.parse(body).length > 0 && JSON.parse(body)[0].segments.length === 0) { + done(); // pass + } else { + done("response had segments"); + } + } + }); + }); + + it('Should be able to get an empty array if no videos', (done: Done) => { + request.get(getbaseURL() + + '/api/skipSegments/11111?categories=["shilling"]', null, + (err, res, body) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode !== 404) done("non 404 status code, was " + res.statusCode); + else { + if (JSON.parse(body).length === 0 && body === '[]') done(); // pass + else done("non empty array returned"); + } + }); + }); + + it('Should return 400 prefix too short', (done: Done) => { + request.get(getbaseURL() + + '/api/skipSegments/11?categories=["shilling"]', null, + (err, res) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode !== 400) done("non 400 status code, was " + res.statusCode); + else { + done(); // pass + } + }); + }); + + it('Should return 400 prefix too long', (done: Done) => { + let prefix = new Array(50).join('1'); + if (prefix.length <= 32) { // default value, config can change this + done('failed to generate a long enough string for the test ' + prefix.length); + return; + } + + request.get(getbaseURL() + + '/api/skipSegments/' + prefix + '?categories=["shilling"]', null, + (err, res) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode !== 400) done("non 400 status code, was " + res.statusCode); + else { + done(); // pass + } + }); + }); + + it('Should not return 400 prefix in range', (done: Done) => { + request.get(getbaseURL() + + '/api/skipSegments/11111?categories=["shilling"]', null, + (err, res) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode === 400) done("prefix length 5 gave 400 " + res.statusCode); + else { + done(); // pass + } + }); + }); + + it('Should return 404 for no hash', (done: Done) => { + request.get(getbaseURL() + + '/api/skipSegments/?categories=["shilling"]', null, + (err, res) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode !== 404) done("expected 404, got " + res.statusCode); + else { + done(); // pass + } + }); + }); + + it('Should return 500 for bad format categories', (done: Done) => { // should probably be 400 + request.get(getbaseURL() + + '/api/skipSegments/?categories=shilling', null, + (err, res) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode !== 500) done("expected 500 got " + res.statusCode); + else { + done(); // pass + } + }); + }); + + it('Should be able to get multiple videos', (done: Done) => { + request.get(getbaseURL() + + '/api/skipSegments/fdaf?categories=["sponsor","intro"]', null, + (err, res, body) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode !== 200) done("non 200 status code, was " + res.statusCode); + else { + body = JSON.parse(body); + if (body.length !== 2) done("expected 2 video, got " + body.length); + else if (body[0].segments.length !== 2) done("expected 2 segments for first video, got " + body[0].segments.length); + else if (body[1].segments.length !== 1) done("expected 1 segment for second video, got " + body[1].segments.length); + else done(); + } + }); + }); + + it('Should be able to get 200 for no categories (default sponsor)', (done: Done) => { + request.get(getbaseURL() + + '/api/skipSegments/fdaf', null, + (err, res, body) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode !== 200) done("non 200 status code, was " + res.statusCode); + else { + body = JSON.parse(body); + if (body.length !== 2) done("expected 2 videos, got " + body.length); + else if (body[0].segments.length !== 1) done("expected 1 segments for first video, got " + body[0].segments.length); + else if (body[1].segments.length !== 1) done("expected 1 segments for second video, got " + body[1].segments.length); + else if (body[0].segments[0].category !== 'sponsor' || body[1].segments[0].category !== 'sponsor') done("both segments are not sponsor"); + else done(); + } + }); + }); + + it('Should be able to post a segment and get it using endpoint', (done: Done) => { + let testID = 'abc123goodVideo'; + request.post(getbaseURL() + + "/api/postVideoSponsorTimes", { + json: { + userID: "test", + videoID: testID, + segments: [{ + segment: [13, 17], + category: "sponsor", + }], + }, + }, + (err, res) => { + if (err) done('(post) ' + err); + else if (res.statusCode === 200) { + request.get(getbaseURL() + + '/api/skipSegments/' + getHash(testID, 1).substring(0, 3), null, + (err, res, body) => { + if (err) done("(get) Couldn't call endpoint"); + else if (res.statusCode !== 200) done("(get) non 200 status code, was " + res.statusCode); + else { + body = JSON.parse(body); + if (body.length !== 1) done("(get) expected 1 video, got " + body.length); + else if (body[0].segments.length !== 1) done("(get) expected 1 segments for first video, got " + body[0].segments.length); + else if (body[0].segments[0].category !== 'sponsor') done("(get) segment should be sponsor, was " + body[0].segments[0].category); + else done(); + } + }); + } else { + done("(post) non 200 status code, was " + res.statusCode); + } + }, + ); + }); +}); diff --git a/test/cases/getSkipSegments.js b/test/cases/getSkipSegments.js deleted file mode 100644 index 0314028..0000000 --- a/test/cases/getSkipSegments.js +++ /dev/null @@ -1,217 +0,0 @@ -var request = require('request'); -var db = require('../../src/databases/databases.js').db; -var utils = require('../utils.js'); -var getHash = require('../../src/utils/getHash.js'); - - -describe('getSkipSegments', () => { - before(() => { - let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden, hashedVideoID) VALUES"; - db.exec(startOfQuery + "('testtesttest', 1, 11, 2, '1-uuid-0', 'testman', 0, 50, 'sponsor', 0, '" + getHash('testtesttest', 0) + "')"); - db.exec(startOfQuery + "('testtesttest', 20, 33, 2, '1-uuid-2', 'testman', 0, 50, 'intro', 0, '" + getHash('testtesttest', 0) + "')"); - db.exec(startOfQuery + "('testtesttest,test', 1, 11, 2, '1-uuid-1', 'testman', 0, 50, 'sponsor', 0, '" + getHash('testtesttest,test', 0) + "')"); - db.exec(startOfQuery + "('test3', 1, 11, 2, '1-uuid-4', 'testman', 0, 50, 'sponsor', 0, '" + getHash('test3', 0) + "')"); - db.exec(startOfQuery + "('test3', 7, 22, -3, '1-uuid-5', 'testman', 0, 50, 'sponsor', 0, '" + getHash('test3', 0) + "')"); - db.exec(startOfQuery + "('multiple', 1, 11, 2, '1-uuid-6', 'testman', 0, 50, 'intro', 0, '" + getHash('multiple', 0) + "')"); - db.exec(startOfQuery + "('multiple', 20, 33, 2, '1-uuid-7', 'testman', 0, 50, 'intro', 0, '" + getHash('multiple', 0) + "')"); - }); - - - it('Should be able to get a time by category 1', (done) => { - request.get(utils.getbaseURL() - + "/api/skipSegments?videoID=testtesttest&category=sponsor", null, - (err, res, body) => { - if (err) done("Couldn't call endpoint"); - else if (res.statusCode !== 200) done("Status code was: " + res.statusCode); - else { - let data = JSON.parse(res.body); - if (data.length === 1 && data[0].segment[0] === 1 && data[0].segment[1] === 11 - && data[0].category === "sponsor" && data[0].UUID === "1-uuid-0") { - done(); - } else { - done("Received incorrect body: " + res.body); - } - } - }); - }); - - it('Should be able to get a time by category 2', (done) => { - request.get(utils.getbaseURL() - + "/api/skipSegments?videoID=testtesttest&category=intro", null, - (err, res, body) => { - if (err) done("Couldn't call endpoint"); - else if (res.statusCode !== 200) done("Status code was: " + res.statusCode); - else { - let data = JSON.parse(res.body); - if (data.length === 1 && data[0].segment[0] === 20 && data[0].segment[1] === 33 - && data[0].category === "intro" && data[0].UUID === "1-uuid-2") { - done(); - } else { - done("Received incorrect body: " + res.body); - } - } - }); - }); - - it('Should be able to get a time by categories array', (done) => { - request.get(utils.getbaseURL() - + "/api/skipSegments?videoID=testtesttest&categories=[\"sponsor\"]", null, - (err, res, body) => { - if (err) done("Couldn't call endpoint"); - else if (res.statusCode !== 200) done("Status code was: " + res.statusCode); - else { - let data = JSON.parse(res.body); - if (data.length === 1 && data[0].segment[0] === 1 && data[0].segment[1] === 11 - && data[0].category === "sponsor" && data[0].UUID === "1-uuid-0") { - done(); - } else { - done("Received incorrect body: " + res.body); - } - } - }); - }); - - it('Should be able to get a time by categories array 2', (done) => { - request.get(utils.getbaseURL() - + "/api/skipSegments?videoID=testtesttest&categories=[\"intro\"]", null, - (err, res, body) => { - if (err) done("Couldn't call endpoint"); - else if (res.statusCode !== 200) done("Status code was: " + res.statusCode); - else { - let data = JSON.parse(res.body); - if (data.length === 1 && data[0].segment[0] === 20 && data[0].segment[1] === 33 - && data[0].category === "intro" && data[0].UUID === "1-uuid-2") { - done(); - } else { - done("Received incorrect body: " + res.body); - } - } - }); - }); - - it('Should be able to get multiple times by category', (done) => { - request.get(utils.getbaseURL() - + "/api/skipSegments?videoID=multiple&categories=[\"intro\"]", null, - (err, res, body) => { - if (err) done("Couldn't call endpoint"); - else if (res.statusCode !== 200) done("Status code was: " + res.statusCode); - else { - let data = JSON.parse(res.body); - if (data.length === 2) { - - let success = true; - for (const segment of data) { - if ((segment.segment[0] !== 20 || segment.segment[1] !== 33 - || segment.category !== "intro" || segment.UUID !== "1-uuid-7") && - (segment.segment[0] !== 1 || segment.segment[1] !== 11 - || segment.category !== "intro" || segment.UUID !== "1-uuid-6")) { - success = false; - break; - } - } - - if (success) done(); - else done("Received incorrect body: " + res.body); - } else { - done("Received incorrect body: " + res.body); - } - } - }); - }); - - it('Should be able to get multiple times by multiple categories', (done) => { - request.get(utils.getbaseURL() - + "/api/skipSegments?videoID=testtesttest&categories=[\"sponsor\", \"intro\"]", null, - (err, res, body) => { - if (err) done("Couldn't call endpoint"); - else if (res.statusCode !== 200) done("Status code was: " + res.statusCode); - else { - let data = JSON.parse(res.body); - if (data.length === 2) { - - let success = true; - for (const segment of data) { - if ((segment.segment[0] !== 20 || segment.segment[1] !== 33 - || segment.category !== "intro" || segment.UUID !== "1-uuid-2") && - (segment.segment[0] !== 1 || segment.segment[1] !== 11 - || segment.category !== "sponsor" || segment.UUID !== "1-uuid-0")) { - success = false; - break; - } - } - - if (success) done(); - else done("Received incorrect body: " + res.body); - } else { - done("Received incorrect body: " + res.body); - } - } - }); - }); - - it('Should be possible to send unexpected query parameters', (done) => { - request.get(utils.getbaseURL() - + "/api/skipSegments?videoID=testtesttest&fakeparam=hello&category=sponsor", null, - (err, res, body) => { - if (err) done("Couldn't call endpoint"); - else if (res.statusCode !== 200) done("Status code was: " + res.statusCode); - else { - let data = JSON.parse(res.body); - if (data.length === 1 && data[0].segment[0] === 1 && data[0].segment[1] === 11 - && data[0].category === "sponsor" && data[0].UUID === "1-uuid-0") { - done(); - } else { - done("Received incorrect body: " + res.body); - } - } - }); - }); - - it('Low voted submissions should be hidden', (done) => { - request.get(utils.getbaseURL() - + "/api/skipSegments?videoID=test3&category=sponsor", null, - (err, res, body) => { - if (err) done("Couldn't call endpoint"); - else if (res.statusCode !== 200) done("Status code was: " + res.statusCode); - else { - let data = JSON.parse(res.body); - if (data.length === 1 && data[0].segment[0] === 1 && data[0].segment[1] === 11 - && data[0].category === "sponsor" && data[0].UUID === "1-uuid-4") { - done(); - } else { - done("Received incorrect body: " + res.body); - } - } - }); - }); - - it('Should return 404 if no segment found', (done) => { - request.get(utils.getbaseURL() - + "/api/skipSegments?videoID=notarealvideo", null, - (err, res, body) => { - if (err) done("couldn't call endpoint"); - else if (res.statusCode !== 404) done("non 404 respone code: " + res.statusCode); - else done(); // pass - }); - }); - - - it('Should be able send a comma in a query param', (done) => { - request.get(utils.getbaseURL() - + "/api/skipSegments?videoID=testtesttest,test&category=sponsor", null, - (err, res, body) => { - if (err) done("Couldn't call endpoint"); - else if (res.statusCode !== 200) done("Status code was: " + res.statusCode); - else { - let data = JSON.parse(res.body); - if (data.length === 1 && data[0].segment[0] === 1 && data[0].segment[1] === 11 - && data[0].category === "sponsor" && data[0].UUID === "1-uuid-1") { - done(); - } else { - done("Received incorrect body: " + res.body); - } - } - }); - }); - -}); \ No newline at end of file diff --git a/test/cases/getSkipSegments.ts b/test/cases/getSkipSegments.ts new file mode 100644 index 0000000..dfc6dc2 --- /dev/null +++ b/test/cases/getSkipSegments.ts @@ -0,0 +1,216 @@ +import request from 'request'; +import {db} from '../../src/databases/databases'; +import {Done, getbaseURL} from '../utils'; +import {getHash} from '../../src/utils/getHash'; + +describe('getSkipSegments', () => { + before(() => { + let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden, hashedVideoID) VALUES"; + db.exec(startOfQuery + "('testtesttest', 1, 11, 2, '1-uuid-0', 'testman', 0, 50, 'sponsor', 0, '" + getHash('testtesttest', 0) + "')"); + db.exec(startOfQuery + "('testtesttest', 20, 33, 2, '1-uuid-2', 'testman', 0, 50, 'intro', 0, '" + getHash('testtesttest', 0) + "')"); + db.exec(startOfQuery + "('testtesttest,test', 1, 11, 2, '1-uuid-1', 'testman', 0, 50, 'sponsor', 0, '" + getHash('testtesttest,test', 0) + "')"); + db.exec(startOfQuery + "('test3', 1, 11, 2, '1-uuid-4', 'testman', 0, 50, 'sponsor', 0, '" + getHash('test3', 0) + "')"); + db.exec(startOfQuery + "('test3', 7, 22, -3, '1-uuid-5', 'testman', 0, 50, 'sponsor', 0, '" + getHash('test3', 0) + "')"); + db.exec(startOfQuery + "('multiple', 1, 11, 2, '1-uuid-6', 'testman', 0, 50, 'intro', 0, '" + getHash('multiple', 0) + "')"); + db.exec(startOfQuery + "('multiple', 20, 33, 2, '1-uuid-7', 'testman', 0, 50, 'intro', 0, '" + getHash('multiple', 0) + "')"); + }); + + + it('Should be able to get a time by category 1', (done: Done) => { + request.get(getbaseURL() + + "/api/skipSegments?videoID=testtesttest&category=sponsor", null, + (err, res) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode !== 200) done("Status code was: " + res.statusCode); + else { + let data = JSON.parse(res.body); + if (data.length === 1 && data[0].segment[0] === 1 && data[0].segment[1] === 11 + && data[0].category === "sponsor" && data[0].UUID === "1-uuid-0") { + done(); + } else { + done("Received incorrect body: " + res.body); + } + } + }); + }); + + it('Should be able to get a time by category 2', (done: Done) => { + request.get(getbaseURL() + + "/api/skipSegments?videoID=testtesttest&category=intro", null, + (err, res) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode !== 200) done("Status code was: " + res.statusCode); + else { + let data = JSON.parse(res.body); + if (data.length === 1 && data[0].segment[0] === 20 && data[0].segment[1] === 33 + && data[0].category === "intro" && data[0].UUID === "1-uuid-2") { + done(); + } else { + done("Received incorrect body: " + res.body); + } + } + }); + }); + + it('Should be able to get a time by categories array', (done: Done) => { + request.get(getbaseURL() + + "/api/skipSegments?videoID=testtesttest&categories=[\"sponsor\"]", null, + (err, res) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode !== 200) done("Status code was: " + res.statusCode); + else { + let data = JSON.parse(res.body); + if (data.length === 1 && data[0].segment[0] === 1 && data[0].segment[1] === 11 + && data[0].category === "sponsor" && data[0].UUID === "1-uuid-0") { + done(); + } else { + done("Received incorrect body: " + res.body); + } + } + }); + }); + + it('Should be able to get a time by categories array 2', (done: Done) => { + request.get(getbaseURL() + + "/api/skipSegments?videoID=testtesttest&categories=[\"intro\"]", null, + (err, res) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode !== 200) done("Status code was: " + res.statusCode); + else { + let data = JSON.parse(res.body); + if (data.length === 1 && data[0].segment[0] === 20 && data[0].segment[1] === 33 + && data[0].category === "intro" && data[0].UUID === "1-uuid-2") { + done(); + } else { + done("Received incorrect body: " + res.body); + } + } + }); + }); + + it('Should be able to get multiple times by category', (done: Done) => { + request.get(getbaseURL() + + "/api/skipSegments?videoID=multiple&categories=[\"intro\"]", null, + (err, res) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode !== 200) done("Status code was: " + res.statusCode); + else { + let data = JSON.parse(res.body); + if (data.length === 2) { + + let success = true; + for (const segment of data) { + if ((segment.segment[0] !== 20 || segment.segment[1] !== 33 + || segment.category !== "intro" || segment.UUID !== "1-uuid-7") && + (segment.segment[0] !== 1 || segment.segment[1] !== 11 + || segment.category !== "intro" || segment.UUID !== "1-uuid-6")) { + success = false; + break; + } + } + + if (success) done(); + else done("Received incorrect body: " + res.body); + } else { + done("Received incorrect body: " + res.body); + } + } + }); + }); + + it('Should be able to get multiple times by multiple categories', (done: Done) => { + request.get(getbaseURL() + + "/api/skipSegments?videoID=testtesttest&categories=[\"sponsor\", \"intro\"]", null, + (err, res) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode !== 200) done("Status code was: " + res.statusCode); + else { + let data = JSON.parse(res.body); + if (data.length === 2) { + + let success = true; + for (const segment of data) { + if ((segment.segment[0] !== 20 || segment.segment[1] !== 33 + || segment.category !== "intro" || segment.UUID !== "1-uuid-2") && + (segment.segment[0] !== 1 || segment.segment[1] !== 11 + || segment.category !== "sponsor" || segment.UUID !== "1-uuid-0")) { + success = false; + break; + } + } + + if (success) done(); + else done("Received incorrect body: " + res.body); + } else { + done("Received incorrect body: " + res.body); + } + } + }); + }); + + it('Should be possible to send unexpected query parameters', (done: Done) => { + request.get(getbaseURL() + + "/api/skipSegments?videoID=testtesttest&fakeparam=hello&category=sponsor", null, + (err, res) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode !== 200) done("Status code was: " + res.statusCode); + else { + let data = JSON.parse(res.body); + if (data.length === 1 && data[0].segment[0] === 1 && data[0].segment[1] === 11 + && data[0].category === "sponsor" && data[0].UUID === "1-uuid-0") { + done(); + } else { + done("Received incorrect body: " + res.body); + } + } + }); + }); + + it('Low voted submissions should be hidden', (done: Done) => { + request.get(getbaseURL() + + "/api/skipSegments?videoID=test3&category=sponsor", null, + (err, res) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode !== 200) done("Status code was: " + res.statusCode); + else { + let data = JSON.parse(res.body); + if (data.length === 1 && data[0].segment[0] === 1 && data[0].segment[1] === 11 + && data[0].category === "sponsor" && data[0].UUID === "1-uuid-4") { + done(); + } else { + done("Received incorrect body: " + res.body); + } + } + }); + }); + + it('Should return 404 if no segment found', (done: Done) => { + request.get(getbaseURL() + + "/api/skipSegments?videoID=notarealvideo", null, + (err, res) => { + if (err) done("couldn't call endpoint"); + else if (res.statusCode !== 404) done("non 404 respone code: " + res.statusCode); + else done(); // pass + }); + }); + + + it('Should be able send a comma in a query param', (done: Done) => { + request.get(getbaseURL() + + "/api/skipSegments?videoID=testtesttest,test&category=sponsor", null, + (err, res) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode !== 200) done("Status code was: " + res.statusCode); + else { + let data = JSON.parse(res.body); + if (data.length === 1 && data[0].segment[0] === 1 && data[0].segment[1] === 11 + && data[0].category === "sponsor" && data[0].UUID === "1-uuid-1") { + done(); + } else { + done("Received incorrect body: " + res.body); + } + } + }); + }); + +}); diff --git a/test/cases/getSubmissionUUID.js b/test/cases/getSubmissionUUID.js deleted file mode 100644 index 75f23f2..0000000 --- a/test/cases/getSubmissionUUID.js +++ /dev/null @@ -1,8 +0,0 @@ -const getSubmissionUUID = require('../../src/utils/getSubmissionUUID.js'); -const assert = require('assert'); - -describe('getSubmissionUUID', () => { - it('Should return the hashed value', () => { - assert.equal(getSubmissionUUID('video001', 'sponsor', 'testuser001', 13.33337, 42.000001), '1d33d7016aa6482849019bd906d75c08fe6b815e64e823146df35f66c35612dd'); - }); -}); diff --git a/test/cases/getSubmissionUUID.ts b/test/cases/getSubmissionUUID.ts new file mode 100644 index 0000000..6b3ab3c --- /dev/null +++ b/test/cases/getSubmissionUUID.ts @@ -0,0 +1,8 @@ +import {getSubmissionUUID} from '../../src/utils/getSubmissionUUID'; +import assert from 'assert'; + +describe('getSubmissionUUID', () => { + it('Should return the hashed value', () => { + assert.strictEqual(getSubmissionUUID('video001', 'sponsor', 'testuser001', 13.33337, 42.000001), '1d33d7016aa6482849019bd906d75c08fe6b815e64e823146df35f66c35612dd'); + }); +}); diff --git a/test/cases/getUserInfo.js b/test/cases/getUserInfo.js deleted file mode 100644 index f04c02f..0000000 --- a/test/cases/getUserInfo.js +++ /dev/null @@ -1,167 +0,0 @@ -var request = require('request'); -var utils = require('../utils.js'); -var db = require('../../src/databases/databases.js').db; -var getHash = require('../../src/utils/getHash.js'); - -describe('getUserInfo', () => { - before(() => { - let startOfUserNamesQuery = "INSERT INTO userNames (userID, userName) VALUES"; - db.exec(startOfUserNamesQuery + "('" + getHash("getuserinfo_user_01") + "', 'Username user 01')"); - let startOfSponsorTimesQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden) VALUES"; - db.exec(startOfSponsorTimesQuery + "('xxxyyyzzz', 1, 11, 2, 'uuid000001', '" + getHash("getuserinfo_user_01") + "', 0, 10, 'sponsor', 0)"); - db.exec(startOfSponsorTimesQuery + "('xxxyyyzzz', 1, 11, 2, 'uuid000002', '" + getHash("getuserinfo_user_01") + "', 0, 10, 'sponsor', 0)"); - db.exec(startOfSponsorTimesQuery + "('yyyxxxzzz', 1, 11, -1, 'uuid000003', '" + getHash("getuserinfo_user_01") + "', 0, 10, 'sponsor', 0)"); - db.exec(startOfSponsorTimesQuery + "('yyyxxxzzz', 1, 11, -2, 'uuid000004', '" + getHash("getuserinfo_user_01") + "', 0, 10, 'sponsor', 1)"); - db.exec(startOfSponsorTimesQuery + "('xzzzxxyyy', 1, 11, -5, 'uuid000005', '" + getHash("getuserinfo_user_01") + "', 0, 10, 'sponsor', 1)"); - db.exec(startOfSponsorTimesQuery + "('zzzxxxyyy', 1, 11, 2, 'uuid000006', '" + getHash("getuserinfo_user_02") + "', 0, 10, 'sponsor', 0)"); - db.exec(startOfSponsorTimesQuery + "('xxxyyyzzz', 1, 11, 2, 'uuid000007', '" + getHash("getuserinfo_user_02") + "', 0, 10, 'sponsor', 1)"); - db.exec(startOfSponsorTimesQuery + "('xxxyyyzzz', 1, 11, 2, 'uuid000008', '" + getHash("getuserinfo_user_02") + "', 0, 10, 'sponsor', 1)"); - - - db.exec("INSERT INTO warnings (userID, issueTime, issuerUserID) VALUES ('" + getHash('getuserinfo_warning_0') + "', 10, 'getuserinfo_vip')"); - db.exec("INSERT INTO warnings (userID, issueTime, issuerUserID) VALUES ('" + getHash('getuserinfo_warning_1') + "', 10, 'getuserinfo_vip')"); - db.exec("INSERT INTO warnings (userID, issueTime, issuerUserID) VALUES ('" + getHash('getuserinfo_warning_1') + "', 10, 'getuserinfo_vip')"); - }); - - it('Should be able to get a 200', (done) => { - request.get(utils.getbaseURL() - + '/api/getUserInfo?userID=getuserinfo_user_01', null, - (err, res, body) => { - if (err) { - done('couldn\'t call endpoint'); - } else { - if (res.statusCode !== 200) { - done('non 200 (' + res.statusCode + ')'); - } else { - done(); // pass - } - } - }); - }); - - it('Should be able to get a 400 (No userID parameter)', (done) => { - request.get(utils.getbaseURL() - + '/api/getUserInfo', null, - (err, res, body) => { - if (err) { - done('couldn\'t call endpoint'); - } else { - if (res.statusCode !== 400) { - done('non 400'); - } else { - done(); // pass - } - } - }); - }); - - it('Should return info', (done) => { - request.get(utils.getbaseURL() - + '/api/getUserInfo?userID=getuserinfo_user_01', null, - (err, res, body) => { - if (err) { - done("couldn't call endpoint"); - } else { - if (res.statusCode !== 200) { - done("non 200"); - } else { - const data = JSON.parse(body); - if (data.userName !== 'Username user 01') { - done('Returned incorrect userName "' + data.userName + '"'); - } else if (data.minutesSaved !== 5) { - done('Returned incorrect minutesSaved "' + data.minutesSaved + '"'); - } else if (data.viewCount !== 30) { - done('Returned incorrect viewCount "' + data.viewCount + '"'); - } else if (data.segmentCount !== 3) { - done('Returned incorrect segmentCount "' + data.segmentCount + '"'); - } else { - done(); // pass - } - } - } - }); - }); - - it('Should get warning data', (done) => { - request.get(utils.getbaseURL() - + '/api/getUserInfo?userID=getuserinfo_warning_0', null, - (err, res, body) => { - if (err) { - done("couldn't call endpoint"); - } else { - if (res.statusCode !== 200) { - done("non 200"); - } else { - const data = JSON.parse(body); - if (data.warnings !== 1) { - done('wrong number of warnings: ' + data.warnings + ', not ' + 1); - } else { - done(); // pass - } - } - } - }); - }); - - it('Should get multiple warnings', (done) => { - request.get(utils.getbaseURL() - + '/api/getUserInfo?userID=getuserinfo_warning_1', null, - (err, res, body) => { - if (err) { - done("couldn't call endpoint"); - } else { - if (res.statusCode !== 200) { - done("non 200"); - } else { - const data = JSON.parse(body); - if (data.warnings !== 2) { - done('wrong number of warnings: ' + data.warnings + ', not ' + 2); - } else { - done(); // pass - } - } - } - }); - }); - - it('Should not get warnings if noe', (done) => { - request.get(utils.getbaseURL() - + '/api/getUserInfo?userID=getuserinfo_warning_2', null, - (err, res, body) => { - if (err) { - done("couldn't call endpoint"); - } else { - if (res.statusCode !== 200) { - done("non 200"); - } else { - const data = JSON.parse(body); - if (data.warnings !== 0) { - done('wrong number of warnings: ' + data.warnings + ', not ' + 0); - } else { - done(); // pass - } - } - } - }); - }); - - it('Should return userID for userName (No userName set)', (done) => { - request.get(utils.getbaseURL() - + '/api/getUserInfo?userID=getuserinfo_user_02', null, - (err, res, body) => { - if (err) { - done('couldn\'t call endpoint'); - } else { - if (res.statusCode !== 200) { - done('non 200'); - } else { - const data = JSON.parse(body); - if (data.userName !== 'c2a28fd225e88f74945794ae85aef96001d4a1aaa1022c656f0dd48ac0a3ea0f') { - return done('Did not return userID for userName'); - } - done(); // pass - } - } - }); - }); -}); diff --git a/test/cases/getUserInfo.ts b/test/cases/getUserInfo.ts new file mode 100644 index 0000000..7724222 --- /dev/null +++ b/test/cases/getUserInfo.ts @@ -0,0 +1,167 @@ +import request from 'request'; +import {Done, getbaseURL} from '../utils'; +import {db} from '../../src/databases/databases'; +import {getHash} from '../../src/utils/getHash'; + +describe('getUserInfo', () => { + before(() => { + let startOfUserNamesQuery = "INSERT INTO userNames (userID, userName) VALUES"; + db.exec(startOfUserNamesQuery + "('" + getHash("getuserinfo_user_01") + "', 'Username user 01')"); + let startOfSponsorTimesQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden) VALUES"; + db.exec(startOfSponsorTimesQuery + "('xxxyyyzzz', 1, 11, 2, 'uuid000001', '" + getHash("getuserinfo_user_01") + "', 0, 10, 'sponsor', 0)"); + db.exec(startOfSponsorTimesQuery + "('xxxyyyzzz', 1, 11, 2, 'uuid000002', '" + getHash("getuserinfo_user_01") + "', 0, 10, 'sponsor', 0)"); + db.exec(startOfSponsorTimesQuery + "('yyyxxxzzz', 1, 11, -1, 'uuid000003', '" + getHash("getuserinfo_user_01") + "', 0, 10, 'sponsor', 0)"); + db.exec(startOfSponsorTimesQuery + "('yyyxxxzzz', 1, 11, -2, 'uuid000004', '" + getHash("getuserinfo_user_01") + "', 0, 10, 'sponsor', 1)"); + db.exec(startOfSponsorTimesQuery + "('xzzzxxyyy', 1, 11, -5, 'uuid000005', '" + getHash("getuserinfo_user_01") + "', 0, 10, 'sponsor', 1)"); + db.exec(startOfSponsorTimesQuery + "('zzzxxxyyy', 1, 11, 2, 'uuid000006', '" + getHash("getuserinfo_user_02") + "', 0, 10, 'sponsor', 0)"); + db.exec(startOfSponsorTimesQuery + "('xxxyyyzzz', 1, 11, 2, 'uuid000007', '" + getHash("getuserinfo_user_02") + "', 0, 10, 'sponsor', 1)"); + db.exec(startOfSponsorTimesQuery + "('xxxyyyzzz', 1, 11, 2, 'uuid000008', '" + getHash("getuserinfo_user_02") + "', 0, 10, 'sponsor', 1)"); + + + db.exec("INSERT INTO warnings (userID, issueTime, issuerUserID) VALUES ('" + getHash('getuserinfo_warning_0') + "', 10, 'getuserinfo_vip')"); + db.exec("INSERT INTO warnings (userID, issueTime, issuerUserID) VALUES ('" + getHash('getuserinfo_warning_1') + "', 10, 'getuserinfo_vip')"); + db.exec("INSERT INTO warnings (userID, issueTime, issuerUserID) VALUES ('" + getHash('getuserinfo_warning_1') + "', 10, 'getuserinfo_vip')"); + }); + + it('Should be able to get a 200', (done: Done) => { + request.get(getbaseURL() + + '/api/getUserInfo?userID=getuserinfo_user_01', null, + (err, res) => { + if (err) { + done('couldn\'t call endpoint'); + } else { + if (res.statusCode !== 200) { + done('non 200 (' + res.statusCode + ')'); + } else { + done(); // pass + } + } + }); + }); + + it('Should be able to get a 400 (No userID parameter)', (done: Done) => { + request.get(getbaseURL() + + '/api/getUserInfo', null, + (err, res) => { + if (err) { + done('couldn\'t call endpoint'); + } else { + if (res.statusCode !== 400) { + done('non 400'); + } else { + done(); // pass + } + } + }); + }); + + it('Should return info', (done: Done) => { + request.get(getbaseURL() + + '/api/getUserInfo?userID=getuserinfo_user_01', null, + (err, res, body) => { + if (err) { + done("couldn't call endpoint"); + } else { + if (res.statusCode !== 200) { + done("non 200"); + } else { + const data = JSON.parse(body); + if (data.userName !== 'Username user 01') { + done('Returned incorrect userName "' + data.userName + '"'); + } else if (data.minutesSaved !== 5) { + done('Returned incorrect minutesSaved "' + data.minutesSaved + '"'); + } else if (data.viewCount !== 30) { + done('Returned incorrect viewCount "' + data.viewCount + '"'); + } else if (data.segmentCount !== 3) { + done('Returned incorrect segmentCount "' + data.segmentCount + '"'); + } else { + done(); // pass + } + } + } + }); + }); + + it('Should get warning data', (done: Done) => { + request.get(getbaseURL() + + '/api/getUserInfo?userID=getuserinfo_warning_0', null, + (err, res, body) => { + if (err) { + done("couldn't call endpoint"); + } else { + if (res.statusCode !== 200) { + done("non 200"); + } else { + const data = JSON.parse(body); + if (data.warnings !== 1) { + done('wrong number of warnings: ' + data.warnings + ', not ' + 1); + } else { + done(); // pass + } + } + } + }); + }); + + it('Should get multiple warnings', (done: Done) => { + request.get(getbaseURL() + + '/api/getUserInfo?userID=getuserinfo_warning_1', null, + (err, res, body) => { + if (err) { + done("couldn't call endpoint"); + } else { + if (res.statusCode !== 200) { + done("non 200"); + } else { + const data = JSON.parse(body); + if (data.warnings !== 2) { + done('wrong number of warnings: ' + data.warnings + ', not ' + 2); + } else { + done(); // pass + } + } + } + }); + }); + + it('Should not get warnings if noe', (done: Done) => { + request.get(getbaseURL() + + '/api/getUserInfo?userID=getuserinfo_warning_2', null, + (err, res, body) => { + if (err) { + done("couldn't call endpoint"); + } else { + if (res.statusCode !== 200) { + done("non 200"); + } else { + const data = JSON.parse(body); + if (data.warnings !== 0) { + done('wrong number of warnings: ' + data.warnings + ', not ' + 0); + } else { + done(); // pass + } + } + } + }); + }); + + it('Should return userID for userName (No userName set)', (done: Done) => { + request.get(getbaseURL() + + '/api/getUserInfo?userID=getuserinfo_user_02', null, + (err, res, body) => { + if (err) { + done('couldn\'t call endpoint'); + } else { + if (res.statusCode !== 200) { + done('non 200'); + } else { + const data = JSON.parse(body); + if (data.userName !== 'c2a28fd225e88f74945794ae85aef96001d4a1aaa1022c656f0dd48ac0a3ea0f') { + return done('Did not return userID for userName'); + } + done(); // pass + } + } + }); + }); +}); diff --git a/test/cases/noSegmentRecords.js b/test/cases/noSegmentRecords.js deleted file mode 100644 index 4167688..0000000 --- a/test/cases/noSegmentRecords.js +++ /dev/null @@ -1,466 +0,0 @@ -var request = require('request'); - -var utils = require('../utils.js'); -const getHash = require('../../src/utils/getHash.js'); - -var databases = require('../../src/databases/databases.js'); -const logger = require('../../src/utils/logger.js'); -var db = databases.db; - -describe('noSegmentRecords', () => { - before(() => { - db.exec("INSERT INTO vipUsers (userID) VALUES ('" + getHash("VIPUser-noSegments") + "')"); - - db.exec("INSERT INTO noSegments (userID, videoID, category) VALUES ('" + getHash("VIPUser-noSegments") + "', 'no-segments-video-id', 'sponsor')"); - db.exec("INSERT INTO noSegments (userID, videoID, category) VALUES ('" + getHash("VIPUser-noSegments") + "', 'no-segments-video-id', 'intro')"); - - db.exec("INSERT INTO noSegments (userID, videoID, category) VALUES ('" + getHash("VIPUser-noSegments") + "', 'no-segments-video-id-1', 'sponsor')"); - db.exec("INSERT INTO noSegments (userID, videoID, category) VALUES ('" + getHash("VIPUser-noSegments") + "', 'no-segments-video-id-1', 'intro')"); - db.exec("INSERT INTO noSegments (userID, videoID, category) VALUES ('" + getHash("VIPUser-noSegments") + "', 'noSubmitVideo', 'sponsor')"); - - db.exec("INSERT INTO noSegments (userID, videoID, category) VALUES ('" + getHash("VIPUser-noSegments") + "', 'delete-record', 'sponsor')"); - - db.exec("INSERT INTO noSegments (userID, videoID, category) VALUES ('" + getHash("VIPUser-noSegments") + "', 'delete-record-1', 'sponsor')"); - db.exec("INSERT INTO noSegments (userID, videoID, category) VALUES ('" + getHash("VIPUser-noSegments") + "', 'delete-record-1', 'intro')"); - }); - - it('Should update the database version when starting the application', (done) => { - let version = db.prepare('get', 'SELECT key, value FROM config where key = ?', ['version']).value; - if (version > 1) done(); - else done('Version isn\'t greater than 1. Version is ' + version); - }); - - it('Should be able to submit categories not in video (http response)', (done) => { - let json = { - videoID: 'no-segments-video-id', - userID: 'VIPUser-noSegments', - categories: [ - 'outro', - 'shilling', - 'shilling', - 'shil ling', - '', - 'intro' - ] - }; - - let expected = { - submitted: [ - 'outro', - 'shilling' - ] - }; - - request.post(utils.getbaseURL() - + "/api/noSegments", {json}, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - if (JSON.stringify(body) === JSON.stringify(expected)) { - done(); - } else { - done("Incorrect response: expected " + JSON.stringify(expected) + " got " + JSON.stringify(body)); - } - } else { - console.log(body); - done("Status code was " + res.statusCode); - } - }); - }); - - it('Should be able to submit categories not in video (sql check)', (done) => { - let json = { - videoID: 'no-segments-video-id-1', - userID: 'VIPUser-noSegments', - categories: [ - 'outro', - 'shilling', - 'shilling', - 'shil ling', - '', - 'intro' - ] - }; - - request.post(utils.getbaseURL() - + "/api/noSegments", {json}, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - let result = db.prepare('all', 'SELECT * FROM noSegments WHERE videoID = ?', ['no-segments-video-id-1']); - if (result.length !== 4) { - console.log(result); - done("Expected 4 entrys in db, got " + result.length); - } else { - done(); - } - } else { - console.log(body); - done("Status code was " + res.statusCode); - } - }); - }); - - it('Should be able to submit categories with _ in the category', (done) => { - let json = { - videoID: 'underscore', - userID: 'VIPUser-noSegments', - categories: [ - 'word_word', - ] - }; - - request.post(utils.getbaseURL() - + "/api/noSegments", {json}, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - let result = db.prepare('all', 'SELECT * FROM noSegments WHERE videoID = ?', ['underscore']); - if (result.length !== 1) { - console.log(result); - done("Expected 1 entrys in db, got " + result.length); - } else { - done(); - } - } else { - console.log(body); - done("Status code was " + res.statusCode); - } - }); - }); - - it('Should be able to submit categories with upper and lower case in the category', (done) => { - let json = { - videoID: 'bothCases', - userID: 'VIPUser-noSegments', - categories: [ - 'wordWord', - ] - }; - - request.post(utils.getbaseURL() - + "/api/noSegments", {json}, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - let result = db.prepare('all', 'SELECT * FROM noSegments WHERE videoID = ?', ['bothCases']); - if (result.length !== 1) { - console.log(result); - done("Expected 1 entrys in db, got " + result.length); - } else { - done(); - } - } else { - console.log(body); - done("Status code was " + res.statusCode); - } - }); - }); - - it('Should not be able to submit categories with $ in the category', (done) => { - let json = { - videoID: 'specialChar', - userID: 'VIPUser-noSegments', - categories: [ - 'word&word', - ] - }; - - request.post(utils.getbaseURL() - + "/api/noSegments", {json}, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - let result = db.prepare('all', 'SELECT * FROM noSegments WHERE videoID = ?', ['specialChar']); - if (result.length !== 0) { - console.log(result); - done("Expected 0 entrys in db, got " + result.length); - } else { - done(); - } - } else { - console.log(body); - done("Status code was " + res.statusCode); - } - }); - }); - - it('Should return 400 for missing params', (done) => { - request.post(utils.getbaseURL() - + "/api/noSegments", {json: {}}, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 400) { - done() - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - it('Should return 400 for no categories', (done) => { - let json = { - videoID: 'test', - userID: 'test', - categories: [] - }; - - request.post(utils.getbaseURL() - + "/api/noSegments", {json}, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 400) { - done() - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - it('Should return 400 for no userID', (done) => { - let json = { - videoID: 'test', - userID: null, - categories: ['sponsor'] - }; - - request.post(utils.getbaseURL() - + "/api/noSegments", {json}, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 400) { - done() - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - it('Should return 400 for no videoID', (done) => { - let json = { - videoID: null, - userID: 'test', - categories: ['sponsor'] - }; - - request.post(utils.getbaseURL() - + "/api/noSegments", {json}, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 400) { - done() - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - it('Should return 400 object categories)', (done) => { - let json = { - videoID: 'test', - userID: 'test', - categories: {} - }; - - request.post(utils.getbaseURL() - + "/api/noSegments", {json}, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 400) { - done() - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - it('Should return 400 bad format categories', (done) => { - let json = { - videoID: 'test', - userID: 'test', - categories: 'sponsor' - }; - - request.post(utils.getbaseURL() - + "/api/noSegments", {json}, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 400) { - done() - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - it('Should return 403 if user is not VIP', (done) => { - let json = { - videoID: 'test', - userID: 'test', - categories: [ - 'sponsor' - ] - }; - - request.post(utils.getbaseURL() - + "/api/noSegments", {json}, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 403) { - done(); - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - it('Should be able to delete a noSegment record', (done) => { - let json = { - videoID: 'delete-record', - userID: 'VIPUser-noSegments', - categories: [ - 'sponsor' - ] - }; - - request.delete(utils.getbaseURL() - + "/api/noSegments", {json}, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - let result = db.prepare('all', 'SELECT * FROM noSegments WHERE videoID = ?', ['delete-record']); - if (result.length === 0) { - done(); - } else { - done("Didn't delete record"); - } - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - it('Should be able to delete one noSegment record without removing another', (done) => { - let json = { - videoID: 'delete-record-1', - userID: 'VIPUser-noSegments', - categories: [ - 'sponsor' - ] - }; - - request.delete(utils.getbaseURL() - + "/api/noSegments", {json}, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - let result = db.prepare('all', 'SELECT * FROM noSegments WHERE videoID = ?', ['delete-record-1']); - if (result.length === 1) { - done(); - } else { - done("Didn't delete record"); - } - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - - /* - * Submission tests in this file do not check database records, only status codes. - * To test the submission code properly see ./test/cases/postSkipSegments.js - */ - - it('Should not be able to submit a segment to a video with a no-segment record (single submission)', (done) => { - request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes", { - json: { - userID: "testman42", - videoID: "noSubmitVideo", - segments: [{ - segment: [20, 40], - category: "sponsor" - }] - } - }, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 403) { - done() - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - it('Should not be able to submit segments to a video where any of the submissions with a no-segment record', (done) => { - request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes", { - json: { - userID: "testman42", - videoID: "noSubmitVideo", - segments: [{ - segment: [20, 40], - category: "sponsor" - },{ - segment: [50, 60], - category: "intro" - }] - } - }, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 403) { - done() - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - - it('Should be able to submit a segment to a video with a different no-segment record', (done) => { - request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes", { - json: { - userID: "testman42", - videoID: "noSubmitVideo", - segments: [{ - segment: [20, 40], - category: "intro" - }] - } - }, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - done() - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - it('Should be able to submit a segment to a video with no no-segment records', (done) => { - request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes", { - json: { - userID: "testman42", - videoID: "normalVideo", - segments: [{ - segment: [20, 40], - category: "intro" - }] - } - }, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - done() - } else { - done("Status code was " + res.statusCode); - } - }); - }); -}); \ No newline at end of file diff --git a/test/cases/noSegmentRecords.ts b/test/cases/noSegmentRecords.ts new file mode 100644 index 0000000..5963550 --- /dev/null +++ b/test/cases/noSegmentRecords.ts @@ -0,0 +1,463 @@ +import request from 'request'; +import {Done, getbaseURL} from '../utils'; +import {getHash} from '../../src/utils/getHash'; +import {db} from '../../src/databases/databases'; + + +describe('noSegmentRecords', () => { + before(() => { + db.exec("INSERT INTO vipUsers (userID) VALUES ('" + getHash("VIPUser-noSegments") + "')"); + + db.exec("INSERT INTO noSegments (userID, videoID, category) VALUES ('" + getHash("VIPUser-noSegments") + "', 'no-segments-video-id', 'sponsor')"); + db.exec("INSERT INTO noSegments (userID, videoID, category) VALUES ('" + getHash("VIPUser-noSegments") + "', 'no-segments-video-id', 'intro')"); + + db.exec("INSERT INTO noSegments (userID, videoID, category) VALUES ('" + getHash("VIPUser-noSegments") + "', 'no-segments-video-id-1', 'sponsor')"); + db.exec("INSERT INTO noSegments (userID, videoID, category) VALUES ('" + getHash("VIPUser-noSegments") + "', 'no-segments-video-id-1', 'intro')"); + db.exec("INSERT INTO noSegments (userID, videoID, category) VALUES ('" + getHash("VIPUser-noSegments") + "', 'noSubmitVideo', 'sponsor')"); + + db.exec("INSERT INTO noSegments (userID, videoID, category) VALUES ('" + getHash("VIPUser-noSegments") + "', 'delete-record', 'sponsor')"); + + db.exec("INSERT INTO noSegments (userID, videoID, category) VALUES ('" + getHash("VIPUser-noSegments") + "', 'delete-record-1', 'sponsor')"); + db.exec("INSERT INTO noSegments (userID, videoID, category) VALUES ('" + getHash("VIPUser-noSegments") + "', 'delete-record-1', 'intro')"); + }); + + it('Should update the database version when starting the application', (done: Done) => { + let version = db.prepare('get', 'SELECT key, value FROM config where key = ?', ['version']).value; + if (version > 1) done(); + else done('Version isn\'t greater than 1. Version is ' + version); + }); + + it('Should be able to submit categories not in video (http response)', (done: Done) => { + let json = { + videoID: 'no-segments-video-id', + userID: 'VIPUser-noSegments', + categories: [ + 'outro', + 'shilling', + 'shilling', + 'shil ling', + '', + 'intro', + ], + }; + + let expected = { + submitted: [ + 'outro', + 'shilling', + ], + }; + + request.post(getbaseURL() + + "/api/noSegments", {json}, + (err, res, body) => { + if (err) done(err); + else if (res.statusCode === 200) { + if (JSON.stringify(body) === JSON.stringify(expected)) { + done(); + } else { + done("Incorrect response: expected " + JSON.stringify(expected) + " got " + JSON.stringify(body)); + } + } else { + console.log(body); + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should be able to submit categories not in video (sql check)', (done: Done) => { + let json = { + videoID: 'no-segments-video-id-1', + userID: 'VIPUser-noSegments', + categories: [ + 'outro', + 'shilling', + 'shilling', + 'shil ling', + '', + 'intro', + ], + }; + + request.post(getbaseURL() + + "/api/noSegments", {json}, + (err, res, body) => { + if (err) done(err); + else if (res.statusCode === 200) { + let result = db.prepare('all', 'SELECT * FROM noSegments WHERE videoID = ?', ['no-segments-video-id-1']); + if (result.length !== 4) { + console.log(result); + done("Expected 4 entrys in db, got " + result.length); + } else { + done(); + } + } else { + console.log(body); + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should be able to submit categories with _ in the category', (done: Done) => { + let json = { + videoID: 'underscore', + userID: 'VIPUser-noSegments', + categories: [ + 'word_word', + ], + }; + + request.post(getbaseURL() + + "/api/noSegments", {json}, + (err, res, body) => { + if (err) done(err); + else if (res.statusCode === 200) { + let result = db.prepare('all', 'SELECT * FROM noSegments WHERE videoID = ?', ['underscore']); + if (result.length !== 1) { + console.log(result); + done("Expected 1 entrys in db, got " + result.length); + } else { + done(); + } + } else { + console.log(body); + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should be able to submit categories with upper and lower case in the category', (done: Done) => { + let json = { + videoID: 'bothCases', + userID: 'VIPUser-noSegments', + categories: [ + 'wordWord', + ], + }; + + request.post(getbaseURL() + + "/api/noSegments", {json}, + (err, res, body) => { + if (err) done(err); + else if (res.statusCode === 200) { + let result = db.prepare('all', 'SELECT * FROM noSegments WHERE videoID = ?', ['bothCases']); + if (result.length !== 1) { + console.log(result); + done("Expected 1 entrys in db, got " + result.length); + } else { + done(); + } + } else { + console.log(body); + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should not be able to submit categories with $ in the category', (done: Done) => { + let json = { + videoID: 'specialChar', + userID: 'VIPUser-noSegments', + categories: [ + 'word&word', + ], + }; + + request.post(getbaseURL() + + "/api/noSegments", {json}, + (err, res, body) => { + if (err) done(err); + else if (res.statusCode === 200) { + let result = db.prepare('all', 'SELECT * FROM noSegments WHERE videoID = ?', ['specialChar']); + if (result.length !== 0) { + console.log(result); + done("Expected 0 entrys in db, got " + result.length); + } else { + done(); + } + } else { + console.log(body); + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should return 400 for missing params', (done: Done) => { + request.post(getbaseURL() + + "/api/noSegments", {json: {}}, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 400) { + done(); + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should return 400 for no categories', (done: Done) => { + let json: any = { + videoID: 'test', + userID: 'test', + categories: [], + }; + + request.post(getbaseURL() + + "/api/noSegments", {json}, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 400) { + done(); + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should return 400 for no userID', (done: Done) => { + let json: any = { + videoID: 'test', + userID: null, + categories: ['sponsor'], + }; + + request.post(getbaseURL() + + "/api/noSegments", {json}, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 400) { + done(); + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should return 400 for no videoID', (done: Done) => { + let json: any = { + videoID: null, + userID: 'test', + categories: ['sponsor'], + }; + + request.post(getbaseURL() + + "/api/noSegments", {json}, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 400) { + done(); + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should return 400 object categories)', (done: Done) => { + let json = { + videoID: 'test', + userID: 'test', + categories: {}, + }; + + request.post(getbaseURL() + + "/api/noSegments", {json}, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 400) { + done(); + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should return 400 bad format categories', (done: Done) => { + let json = { + videoID: 'test', + userID: 'test', + categories: 'sponsor', + }; + + request.post(getbaseURL() + + "/api/noSegments", {json}, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 400) { + done(); + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should return 403 if user is not VIP', (done: Done) => { + let json = { + videoID: 'test', + userID: 'test', + categories: [ + 'sponsor', + ], + }; + + request.post(getbaseURL() + + "/api/noSegments", {json}, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 403) { + done(); + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should be able to delete a noSegment record', (done: Done) => { + let json = { + videoID: 'delete-record', + userID: 'VIPUser-noSegments', + categories: [ + 'sponsor', + ], + }; + + request.delete(getbaseURL() + + "/api/noSegments", {json}, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 200) { + let result = db.prepare('all', 'SELECT * FROM noSegments WHERE videoID = ?', ['delete-record']); + if (result.length === 0) { + done(); + } else { + done("Didn't delete record"); + } + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should be able to delete one noSegment record without removing another', (done: Done) => { + let json = { + videoID: 'delete-record-1', + userID: 'VIPUser-noSegments', + categories: [ + 'sponsor', + ], + }; + + request.delete(getbaseURL() + + "/api/noSegments", {json}, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 200) { + let result = db.prepare('all', 'SELECT * FROM noSegments WHERE videoID = ?', ['delete-record-1']); + if (result.length === 1) { + done(); + } else { + done("Didn't delete record"); + } + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + + /* + * Submission tests in this file do not check database records, only status codes. + * To test the submission code properly see ./test/cases/postSkipSegments.js + */ + + it('Should not be able to submit a segment to a video with a no-segment record (single submission)', (done: Done) => { + request.post(getbaseURL() + + "/api/postVideoSponsorTimes", { + json: { + userID: "testman42", + videoID: "noSubmitVideo", + segments: [{ + segment: [20, 40], + category: "sponsor", + }], + }, + }, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 403) { + done(); + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should not be able to submit segments to a video where any of the submissions with a no-segment record', (done: Done) => { + request.post(getbaseURL() + + "/api/postVideoSponsorTimes", { + json: { + userID: "testman42", + videoID: "noSubmitVideo", + segments: [{ + segment: [20, 40], + category: "sponsor", + }, { + segment: [50, 60], + category: "intro", + }], + }, + }, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 403) { + done(); + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + + it('Should be able to submit a segment to a video with a different no-segment record', (done: Done) => { + request.post(getbaseURL() + + "/api/postVideoSponsorTimes", { + json: { + userID: "testman42", + videoID: "noSubmitVideo", + segments: [{ + segment: [20, 40], + category: "intro", + }], + }, + }, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 200) { + done(); + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should be able to submit a segment to a video with no no-segment records', (done: Done) => { + request.post(getbaseURL() + + "/api/postVideoSponsorTimes", { + json: { + userID: "testman42", + videoID: "normalVideo", + segments: [{ + segment: [20, 40], + category: "intro", + }], + }, + }, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 200) { + done(); + } else { + done("Status code was " + res.statusCode); + } + }); + }); +}); diff --git a/test/cases/oldGetSponsorTime.js b/test/cases/oldGetSponsorTime.js deleted file mode 100644 index d4b9a88..0000000 --- a/test/cases/oldGetSponsorTime.js +++ /dev/null @@ -1,88 +0,0 @@ -var request = require('request'); -var db = require('../../src/databases/databases.js').db; -var utils = require('../utils.js'); -var getHash = require('../../src/utils/getHash.js'); - - -/* - *CREATE TABLE IF NOT EXISTS "sponsorTimes" ( - "videoID" TEXT NOT NULL, - "startTime" REAL NOT NULL, - "endTime" REAL NOT NULL, - "votes" INTEGER NOT NULL, - "UUID" TEXT NOT NULL UNIQUE, - "userID" TEXT NOT NULL, - "timeSubmitted" INTEGER NOT NULL, - "views" INTEGER NOT NULL, - "shadowHidden" INTEGER NOT NULL -); - */ - -describe('getVideoSponsorTime (Old get method)', () => { - before(() => { - let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden, hashedVideoID) VALUES"; - db.exec(startOfQuery + "('old-testtesttest', 1, 11, 2, 'uuid-0', 'testman', 0, 50, 'sponsor', 0, '" + getHash('old-testtesttest', 1) + "')"); - db.exec(startOfQuery + "('old-testtesttest,test', 1, 11, 2, 'uuid-1', 'testman', 0, 50, 'sponsor', 0, '" + getHash('old-testtesttest,test', 1) + "')"); - }); - - it('Should be able to get a time', (done) => { - request.get(utils.getbaseURL() - + "/api/getVideoSponsorTimes?videoID=old-testtesttest", null, - (err, res, body) => { - if (err) done("Couldn't call endpoint"); - else if (res.statusCode !== 200) done("non 200"); - else done(); // pass - }); - }); - - it('Should return 404 if no segment found', (done) => { - request.get(utils.getbaseURL() - + "/api/getVideoSponsorTimes?videoID=notarealvideo", null, - (err, res, body) => { - if (err) done("couldn't call endpoint"); - else if (res.statusCode !== 404) done("non 404 respone code: " + res.statusCode); - else done(); // pass - }); - }); - - - it('Should be possible to send unexpected query parameters', (done) => { - request.get(utils.getbaseURL() - + "/api/getVideoSponsorTimes?videoID=old-testtesttest&fakeparam=hello", null, - (err, res, body) => { - if (err) done("couldn't callendpoint"); - else if (res.statusCode !== 200) done("non 200"); - else done(); // pass - }); - }); - - it('Should be able send a comma in a query param', (done) => { - request.get(utils.getbaseURL() - + "/api/getVideoSponsorTimes?videoID=old-testtesttest,test", null, - (err, res, body) => { - if (err) done("couln't call endpoint"); - else if (res.statusCode !== 200) done("non 200 response: " + res.statusCode); - else if (JSON.parse(body).UUIDs[0] === 'uuid-1') done(); // pass - else done("couldn't parse response"); - }); - }); - - it('Should be able to get the correct time', (done) => { - request.get(utils.getbaseURL() - + "/api/getVideoSponsorTimes?videoID=old-testtesttest", null, - (err, res, body) => { - if (err) done("couldn't call endpoint"); - else if (res.statusCode !== 200) done("non 200"); - else { - let parsedBody = JSON.parse(body); - if (parsedBody.sponsorTimes[0][0] === 1 - && parsedBody.sponsorTimes[0][1] === 11 - && parsedBody.UUIDs[0] === 'uuid-0') { - done(); // pass - } else { - done("Wrong data was returned + " + parsedBody); - } - }; - }); - }); -}); \ No newline at end of file diff --git a/test/cases/oldGetSponsorTime.ts b/test/cases/oldGetSponsorTime.ts new file mode 100644 index 0000000..d84315d --- /dev/null +++ b/test/cases/oldGetSponsorTime.ts @@ -0,0 +1,87 @@ +import request from 'request'; +import {db} from '../../src/databases/databases'; +import {Done, getbaseURL} from '../utils'; +import {getHash} from '../../src/utils/getHash'; +/* + *CREATE TABLE IF NOT EXISTS "sponsorTimes" ( + "videoID" TEXT NOT NULL, + "startTime" REAL NOT NULL, + "endTime" REAL NOT NULL, + "votes" INTEGER NOT NULL, + "UUID" TEXT NOT NULL UNIQUE, + "userID" TEXT NOT NULL, + "timeSubmitted" INTEGER NOT NULL, + "views" INTEGER NOT NULL, + "shadowHidden" INTEGER NOT NULL +); + */ + +describe('getVideoSponsorTime (Old get method)', () => { + before(() => { + let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden, hashedVideoID) VALUES"; + db.exec(startOfQuery + "('old-testtesttest', 1, 11, 2, 'uuid-0', 'testman', 0, 50, 'sponsor', 0, '" + getHash('old-testtesttest', 1) + "')"); + db.exec(startOfQuery + "('old-testtesttest,test', 1, 11, 2, 'uuid-1', 'testman', 0, 50, 'sponsor', 0, '" + getHash('old-testtesttest,test', 1) + "')"); + }); + + it('Should be able to get a time', (done: Done) => { + request.get(getbaseURL() + + "/api/getVideoSponsorTimes?videoID=old-testtesttest", null, + (err, res) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode !== 200) done("non 200"); + else done(); // pass + }); + }); + + it('Should return 404 if no segment found', (done: Done) => { + request.get(getbaseURL() + + "/api/getVideoSponsorTimes?videoID=notarealvideo", null, + (err, res) => { + if (err) done("couldn't call endpoint"); + else if (res.statusCode !== 404) done("non 404 respone code: " + res.statusCode); + else done(); // pass + }); + }); + + + it('Should be possible to send unexpected query parameters', (done: Done) => { + request.get(getbaseURL() + + "/api/getVideoSponsorTimes?videoID=old-testtesttest&fakeparam=hello", null, + (err, res) => { + if (err) done("couldn't callendpoint"); + else if (res.statusCode !== 200) done("non 200"); + else done(); // pass + }); + }); + + it('Should be able send a comma in a query param', (done: Done) => { + request.get(getbaseURL() + + "/api/getVideoSponsorTimes?videoID=old-testtesttest,test", null, + (err, res, body) => { + if (err) done("couln't call endpoint"); + else if (res.statusCode !== 200) done("non 200 response: " + res.statusCode); + else if (JSON.parse(body).UUIDs[0] === 'uuid-1') done(); // pass + else done("couldn't parse response"); + }); + }); + + it('Should be able to get the correct time', (done: Done) => { + request.get(getbaseURL() + + "/api/getVideoSponsorTimes?videoID=old-testtesttest", null, + (err, res, body) => { + if (err) done("couldn't call endpoint"); + else if (res.statusCode !== 200) done("non 200"); + else { + let parsedBody = JSON.parse(body); + if (parsedBody.sponsorTimes[0][0] === 1 + && parsedBody.sponsorTimes[0][1] === 11 + && parsedBody.UUIDs[0] === 'uuid-0') { + done(); // pass + } else { + done("Wrong data was returned + " + parsedBody); + } + } + + }); + }); +}); diff --git a/test/cases/oldSubmitSponsorTimes.js b/test/cases/oldSubmitSponsorTimes.js deleted file mode 100644 index 31f90ba..0000000 --- a/test/cases/oldSubmitSponsorTimes.js +++ /dev/null @@ -1,55 +0,0 @@ -var assert = require('assert'); -var request = require('request'); - -var utils = require('../utils.js'); - -var databases = require('../../src/databases/databases.js'); -var db = databases.db; - -describe('postVideoSponsorTime (Old submission method)', () => { - it('Should be able to submit a time (GET)', (done) => { - request.get(utils.getbaseURL() - + "/api/postVideoSponsorTimes?videoID=dQw4w9WgXcQ&startTime=1&endTime=10&userID=test", null, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - let row = db.prepare('get', "SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?", ["dQw4w9WgXcQ"]); - if (row.startTime === 1 && row.endTime === 10 && row.category === "sponsor") { - done() - } else { - done("Submitted times were not saved. Actual submission: " + JSON.stringify(row)); - } - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - it('Should be able to submit a time (POST)', (done) => { - request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes?videoID=dQw4w9WgXcE&startTime=1&endTime=11&userID=test", null, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - let row = db.prepare('get', "SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?", ["dQw4w9WgXcE"]); - if (row.startTime === 1 && row.endTime === 11 && row.category === "sponsor") { - done() - } else { - done("Submitted times were not saved. Actual submission: " + JSON.stringify(row)); - } - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - it('Should return 400 for missing params', (done) => { - request.get(utils.getbaseURL() - + "/api/postVideoSponsorTimes?startTime=1&endTime=10&userID=test", null, - (err, res, body) => { - if (err) done(err); - if (res.statusCode === 400) done(); - else done("Status code was: " + res.statusCode); - }); - }); -}); \ No newline at end of file diff --git a/test/cases/oldSubmitSponsorTimes.ts b/test/cases/oldSubmitSponsorTimes.ts new file mode 100644 index 0000000..c2bf338 --- /dev/null +++ b/test/cases/oldSubmitSponsorTimes.ts @@ -0,0 +1,52 @@ +import request from 'request'; +import {Done, getbaseURL} from '../utils'; +import {db} from '../../src/databases/databases'; + + +describe('postVideoSponsorTime (Old submission method)', () => { + it('Should be able to submit a time (GET)', (done: Done) => { + request.get(getbaseURL() + + "/api/postVideoSponsorTimes?videoID=dQw4w9WgXcQ&startTime=1&endTime=10&userID=test", null, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 200) { + let row = db.prepare('get', "SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?", ["dQw4w9WgXcQ"]); + if (row.startTime === 1 && row.endTime === 10 && row.category === "sponsor") { + done(); + } else { + done("Submitted times were not saved. Actual submission: " + JSON.stringify(row)); + } + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should be able to submit a time (POST)', (done: Done) => { + request.post(getbaseURL() + + "/api/postVideoSponsorTimes?videoID=dQw4w9WgXcE&startTime=1&endTime=11&userID=test", null, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 200) { + let row = db.prepare('get', "SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?", ["dQw4w9WgXcE"]); + if (row.startTime === 1 && row.endTime === 11 && row.category === "sponsor") { + done(); + } else { + done("Submitted times were not saved. Actual submission: " + JSON.stringify(row)); + } + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should return 400 for missing params', (done: Done) => { + request.get(getbaseURL() + + "/api/postVideoSponsorTimes?startTime=1&endTime=10&userID=test", null, + (err, res) => { + if (err) done(err); + if (res.statusCode === 400) done(); + else done("Status code was: " + res.statusCode); + }); + }); +}); diff --git a/test/cases/postSkipSegments.js b/test/cases/postSkipSegments.js deleted file mode 100644 index 0a37aaa..0000000 --- a/test/cases/postSkipSegments.js +++ /dev/null @@ -1,455 +0,0 @@ -var assert = require('assert'); -var request = require('request'); -var config = require('../../src/config.js'); -var getHash = require('../../src/utils/getHash.js'); - -var utils = require('../utils.js'); - -var databases = require('../../src/databases/databases.js'); -var db = databases.db; - -describe('postSkipSegments', () => { - before(() => { - let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden, hashedVideoID) VALUES"; - - db.exec(startOfQuery + "('80percent_video', 0, 1000, 0, '80percent-uuid-0', '" + getHash("test") + "', 0, 0, 'interaction', 0, '80percent_video')"); - db.exec(startOfQuery + "('80percent_video', 1001, 1005, 0, '80percent-uuid-1', '" + getHash("test") + "', 0, 0, 'interaction', 0, '80percent_video')"); - db.exec(startOfQuery + "('80percent_video', 0, 5000, -2, '80percent-uuid-2', '" + getHash("test") + "', 0, 0, 'interaction', 0, '80percent_video')"); - - const now = Date.now(); - const warnVip01Hash = getHash("warn-vip01"); - const warnUser01Hash = getHash("warn-user01"); - const warnUser02Hash = getHash("warn-user02"); - const MILLISECONDS_IN_HOUR = 3600000; - const warningExpireTime = MILLISECONDS_IN_HOUR * config.hoursAfterWarningExpires; - const startOfWarningQuery = 'INSERT INTO warnings (userID, issueTime, issuerUserID) VALUES'; - db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + now + "', '" + warnVip01Hash + "')"); - db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + (now-1000) + "', '" + warnVip01Hash + "')"); - db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + (now-2000) + "', '" + warnVip01Hash + "')"); - db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + (now-3601000) + "', '" + warnVip01Hash + "')"); - db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + now + "', '" + warnVip01Hash + "')"); - db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + now + "', '" + warnVip01Hash + "')"); - db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + (now-(warningExpireTime + 1000)) + "', '" + warnVip01Hash + "')"); - db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + (now-(warningExpireTime + 2000)) + "', '" + warnVip01Hash + "')"); - }); - - it('Should be able to submit a single time (Params method)', (done) => { - request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes?videoID=dQw4w9WgXcR&startTime=2&endTime=10&userID=test&category=sponsor", null, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - let row = db.prepare('get', "SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?", ["dQw4w9WgXcR"]); - if (row.startTime === 2 && row.endTime === 10 && row.category === "sponsor") { - done() - } else { - done("Submitted times were not saved. Actual submission: " + JSON.stringify(row)); - } - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - it('Should be able to submit a single time (JSON method)', (done) => { - request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes", { - json: { - userID: "test", - videoID: "dQw4w9WgXcF", - segments: [{ - segment: [0, 10], - category: "sponsor" - }] - } - }, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - let row = db.prepare('get', "SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?", ["dQw4w9WgXcF"]); - if (row.startTime === 0 && row.endTime === 10 && row.category === "sponsor") { - done() - } else { - done("Submitted times were not saved. Actual submission: " + JSON.stringify(row)); - } - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - it('Should be able to submit multiple times (JSON method)', (done) => { - request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes", { - json: { - userID: "test", - videoID: "dQw4w9WgXcQ", - segments: [{ - segment: [3, 10], - category: "sponsor" - }, { - segment: [30, 60], - category: "intro" - }] - } - }, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - let rows = db.prepare('all', "SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?", ["dQw4w9WgXcR"]); - let success = true; - if (rows.length === 2) { - for (const row of rows) { - if ((row.startTime !== 3 || row.endTime !== 10 || row.category !== "sponsor") && - (row.startTime !== 30 || row.endTime !== 60 || row.category !== "intro")) { - success = false; - break; - } - } - } - - if (success) done(); - else done("Submitted times were not saved. Actual submissions: " + JSON.stringify(row)); - } else { - done("Status code was " + res.statusCode); - } - }); - }).timeout(5000); - - it('Should allow multiple times if total is under 80% of video(JSON method)', (done) => { - request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes", { - json: { - userID: "test", - videoID: "L_jWHffIx5E", - segments: [{ - segment: [3, 3000], - category: "sponsor" - },{ - segment: [3002, 3050], - category: "intro" - },{ - segment: [45, 100], - category: "interaction" - },{ - segment: [99, 170], - category: "sponsor" - }] - } - }, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - let rows = db.prepare('all', "SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ? and votes > -1", ["L_jWHffIx5E"]); - let success = true; - if (rows.length === 4) { - for (const row of rows) { - if ((row.startTime !== 3 || row.endTime !== 3000 || row.category !== "sponsor") && - (row.startTime !== 3002 || row.endTime !== 3050 || row.category !== "intro") && - (row.startTime !== 45 || row.endTime !== 100 || row.category !== "interaction") && - (row.startTime !== 99 || row.endTime !== 170 || row.category !== "sponsor")) { - success = false; - break; - } - } - } - - if (success) done(); - else done("Submitted times were not saved. Actual submissions: " + JSON.stringify(rows)); - } else { - done("Status code was " + res.statusCode); - } - }); - }).timeout(5000); - - it('Should reject multiple times if total is over 80% of video (JSON method)', (done) => { - request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes", { - json: { - userID: "test", - videoID: "n9rIGdXnSJc", - segments: [{ - segment: [0, 2000], - category: "interaction" - },{ - segment: [3000, 4000], - category: "sponsor" - },{ - segment: [1500, 2750], - category: "sponsor" - },{ - segment: [4050, 4750], - category: "intro" - }] - } - }, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 400) { - let rows = db.prepare('all', "SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ? and votes > -1", ["n9rIGdXnSJc"]); - let success = true; - if (rows.length === 4) { - for (const row of rows) { - if ((row.startTime === 0 || row.endTime === 2000 || row.category === "interaction") || - (row.startTime === 3000 || row.endTime === 4000 || row.category === "sponsor") || - (row.startTime === 1500 || row.endTime === 2750 || row.category === "sponsor") || - (row.startTime === 4050 || row.endTime === 4750 || row.category === "intro")) { - success = false; - break; - } - } - } - - if (success) done(); - else - done("Submitted times were not saved. Actual submissions: " + JSON.stringify(rows)); - } else { - done("Status code was " + res.statusCode); - } - }); - }).timeout(5000); - - it('Should reject multiple times if total is over 80% of video including previosuly submitted times(JSON method)', (done) => { - request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes", { - json: { - userID: "test", - videoID: "80percent_video", - segments: [{ - segment: [2000, 4000], - category: "sponsor" - },{ - segment: [1500, 2750], - category: "sponsor" - },{ - segment: [4050, 4750], - category: "sponsor" - }] - } - }, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 400) { - let rows = db.prepare('all', "SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ? and votes > -1", ["80percent_video"]); - let success = true && rows.length == 2; - for (const row of rows) { - if ((row.startTime === 2000 || row.endTime === 4000 || row.category === "sponsor") || - (row.startTime === 1500 || row.endTime === 2750 || row.category === "sponsor") || - (row.startTime === 4050 || row.endTime === 4750 || row.category === "sponsor")) { - success = false; - break; - } - } - if (success) done(); - else - done("Submitted times were not saved. Actual submissions: " + JSON.stringify(rows)); - } else { - done("Status code was " + res.statusCode); - } - }); - }).timeout(5000); - - it('Should be accepted if a non-sponsor is less than 1 second', (done) => { - request.post(utils.getbaseURL() - + "/api/skipSegments?videoID=qqwerty&startTime=30&endTime=30.5&userID=testing&category=intro", null, - (err, res, body) => { - if (err) done("Couldn't call endpoint"); - else if (res.statusCode === 200) done(); // pass - else done("non 200 status code: " + res.statusCode + " ("+body+")"); - }); - }); - - it('Should be rejected if a sponsor is less than 1 second', (done) => { - request.post(utils.getbaseURL() - + "/api/skipSegments?videoID=qqwerty&startTime=30&endTime=30.5&userID=testing", null, - (err, res, body) => { - if (err) done("Couldn't call endpoint"); - else if (res.statusCode === 400) done(); // pass - else done("non 403 status code: " + res.statusCode + " ("+body+")"); - }); - }); - - it('Should be rejected if over 80% of the video', (done) => { - request.get(utils.getbaseURL() - + "/api/postVideoSponsorTimes?videoID=qqwerty&startTime=30&endTime=1000000&userID=testing", null, - (err, res, body) => { - if (err) done("Couldn't call endpoint"); - else if (res.statusCode === 403) done(); // pass - else done("non 403 status code: " + res.statusCode + " ("+body+")"); - }); - }); - - it("Should be rejected if NB's predicted probability is <70%.", (done) => { - request.get(utils.getbaseURL() - + "/api/postVideoSponsorTimes?videoID=LevkAjUE6d4&startTime=40&endTime=60&userID=testing", null, - (err, res, body) => { - if (err) done("Couldn't call endpoint"); - else if (res.statusCode === 200) done(); // pass - else done("non 200 status code: " + res.statusCode + " ("+body+")"); - }); - }); - - it('Should be rejected if user has to many active warnings', (done) => { - request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes", { - json: { - userID: "warn-user01", - videoID: "dQw4w9WgXcF", - segments: [{ - segment: [0, 10], - category: "sponsor" - }] - } - }, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 403) { - done(); // success - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - it('Should be accepted if user has some active warnings', (done) => { - request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes", { - json: { - userID: "warn-user02", - videoID: "dQw4w9WgXcF", - segments: [{ - segment: [50, 60], - category: "sponsor" - }] - } - }, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - done(); // success - } else { - done("Status code was " + res.statusCode + " " + body); - } - }); - }); - - it('Should be allowed if youtube thinks duration is 0', (done) => { - request.get(utils.getbaseURL() - + "/api/postVideoSponsorTimes?videoID=noDuration&startTime=30&endTime=10000&userID=testing", null, - (err, res, body) => { - if (err) done("Couldn't call endpoint"); - else if (res.statusCode === 200) done(); // pass - else done("non 200 status code: " + res.statusCode + " ("+body+")"); - }); - }); - - it('Should be rejected if not a valid videoID', (done) => { - request.get(utils.getbaseURL() - + "/api/postVideoSponsorTimes?videoID=knownWrongID&startTime=30&endTime=1000000&userID=testing", null, - (err, res, body) => { - if (err) done("Couldn't call endpoint"); - else if (res.statusCode === 403) done(); // pass - else done("non 403 status code: " + res.statusCode + " ("+body+")"); - }); - }); - - it('Should return 400 for missing params (Params method)', (done) => { - request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes?startTime=9&endTime=10&userID=test", null, - (err, res, body) => { - if (err) done(true); - if (res.statusCode === 400) done(); - else done(true); - }); - }); - - it('Should return 400 for missing params (JSON method) 1', (done) => { - request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes", { - json: { - userID: "test", - segments: [{ - segment: [9, 10], - category: "sponsor" - }, { - segment: [31, 60], - category: "intro" - }] - } - }, - (err, res, body) => { - if (err) done(true); - else if (res.statusCode === 400) done(); - else done(true); - }); - }); - it('Should return 400 for missing params (JSON method) 2', (done) => { - request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes", { - json: { - userID: "test", - videoID: "dQw4w9WgXcQ" - } - }, - (err, res, body) => { - if (err) done(true); - else if (res.statusCode === 400) done(); - else done(true); - }); - }); - it('Should return 400 for missing params (JSON method) 3', (done) => { - request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes", { - json: { - userID: "test", - videoID: "dQw4w9WgXcQ", - segments: [{ - segment: [0], - category: "sponsor" - }, { - segment: [31, 60], - category: "intro" - }] - } - }, - (err, res, body) => { - if (err) done(true); - else if (res.statusCode === 400) done(); - else done(true); - }); - }); - it('Should return 400 for missing params (JSON method) 4', (done) => { - request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes", { - json: { - userID: "test", - videoID: "dQw4w9WgXcQ", - segments: [{ - segment: [9, 10] - }, { - segment: [31, 60], - category: "intro" - }] - } - }, - (err, res, body) => { - if (err) done(true); - else if (res.statusCode === 400) done(); - else done(true); - }); - }); - it('Should return 400 for missing params (JSON method) 5', (done) => { - request.post(utils.getbaseURL() - + "/api/postVideoSponsorTimes", { - json: { - userID: "test", - videoID: "dQw4w9WgXcQ" - } - }, - (err, res, body) => { - if (err) done(true); - else if (res.statusCode === 400) done(); - else done(true); - }); - }); -}); diff --git a/test/cases/postSkipSegments.ts b/test/cases/postSkipSegments.ts new file mode 100644 index 0000000..7423c28 --- /dev/null +++ b/test/cases/postSkipSegments.ts @@ -0,0 +1,451 @@ +import request from 'request'; +import {config} from '../../src/config'; +import {getHash} from '../../src/utils/getHash'; +import {Done, getbaseURL} from '../utils'; +import {db} from '../../src/databases/databases'; + +describe('postSkipSegments', () => { + before(() => { + let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden, hashedVideoID) VALUES"; + + db.exec(startOfQuery + "('80percent_video', 0, 1000, 0, '80percent-uuid-0', '" + getHash("test") + "', 0, 0, 'interaction', 0, '80percent_video')"); + db.exec(startOfQuery + "('80percent_video', 1001, 1005, 0, '80percent-uuid-1', '" + getHash("test") + "', 0, 0, 'interaction', 0, '80percent_video')"); + db.exec(startOfQuery + "('80percent_video', 0, 5000, -2, '80percent-uuid-2', '" + getHash("test") + "', 0, 0, 'interaction', 0, '80percent_video')"); + + const now = Date.now(); + const warnVip01Hash = getHash("warn-vip01"); + const warnUser01Hash = getHash("warn-user01"); + const warnUser02Hash = getHash("warn-user02"); + const MILLISECONDS_IN_HOUR = 3600000; + const warningExpireTime = MILLISECONDS_IN_HOUR * config.hoursAfterWarningExpires; + const startOfWarningQuery = 'INSERT INTO warnings (userID, issueTime, issuerUserID) VALUES'; + db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + now + "', '" + warnVip01Hash + "')"); + db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + (now - 1000) + "', '" + warnVip01Hash + "')"); + db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + (now - 2000) + "', '" + warnVip01Hash + "')"); + db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + (now - 3601000) + "', '" + warnVip01Hash + "')"); + db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + now + "', '" + warnVip01Hash + "')"); + db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + now + "', '" + warnVip01Hash + "')"); + db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + (now - (warningExpireTime + 1000)) + "', '" + warnVip01Hash + "')"); + db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + (now - (warningExpireTime + 2000)) + "', '" + warnVip01Hash + "')"); + }); + + it('Should be able to submit a single time (Params method)', (done: Done) => { + request.post(getbaseURL() + + "/api/postVideoSponsorTimes?videoID=dQw4w9WgXcR&startTime=2&endTime=10&userID=test&category=sponsor", null, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 200) { + let row = db.prepare('get', "SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?", ["dQw4w9WgXcR"]); + if (row.startTime === 2 && row.endTime === 10 && row.category === "sponsor") { + done(); + } else { + done("Submitted times were not saved. Actual submission: " + JSON.stringify(row)); + } + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should be able to submit a single time (JSON method)', (done: Done) => { + request.post(getbaseURL() + + "/api/postVideoSponsorTimes", { + json: { + userID: "test", + videoID: "dQw4w9WgXcF", + segments: [{ + segment: [0, 10], + category: "sponsor", + }], + }, + }, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 200) { + let row = db.prepare('get', "SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?", ["dQw4w9WgXcF"]); + if (row.startTime === 0 && row.endTime === 10 && row.category === "sponsor") { + done(); + } else { + done("Submitted times were not saved. Actual submission: " + JSON.stringify(row)); + } + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should be able to submit multiple times (JSON method)', (done: Done) => { + request.post(getbaseURL() + + "/api/postVideoSponsorTimes", { + json: { + userID: "test", + videoID: "dQw4w9WgXcQ", + segments: [{ + segment: [3, 10], + category: "sponsor", + }, { + segment: [30, 60], + category: "intro", + }], + }, + }, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 200) { + let rows = db.prepare('all', "SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ?", ["dQw4w9WgXcR"]); + let success = true; + if (rows.length === 2) { + for (const row of rows) { + if ((row.startTime !== 3 || row.endTime !== 10 || row.category !== "sponsor") && + (row.startTime !== 30 || row.endTime !== 60 || row.category !== "intro")) { + success = false; + break; + } + } + } + + if (success) done(); + else done("Submitted times were not saved. Actual submissions: " + JSON.stringify(rows)); + } else { + done("Status code was " + res.statusCode); + } + }); + }).timeout(5000); + + it('Should allow multiple times if total is under 80% of video(JSON method)', (done: Done) => { + request.post(getbaseURL() + + "/api/postVideoSponsorTimes", { + json: { + userID: "test", + videoID: "L_jWHffIx5E", + segments: [{ + segment: [3, 3000], + category: "sponsor", + }, { + segment: [3002, 3050], + category: "intro", + }, { + segment: [45, 100], + category: "interaction", + }, { + segment: [99, 170], + category: "sponsor", + }], + }, + }, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 200) { + let rows = db.prepare('all', "SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ? and votes > -1", ["L_jWHffIx5E"]); + let success = true; + if (rows.length === 4) { + for (const row of rows) { + if ((row.startTime !== 3 || row.endTime !== 3000 || row.category !== "sponsor") && + (row.startTime !== 3002 || row.endTime !== 3050 || row.category !== "intro") && + (row.startTime !== 45 || row.endTime !== 100 || row.category !== "interaction") && + (row.startTime !== 99 || row.endTime !== 170 || row.category !== "sponsor")) { + success = false; + break; + } + } + } + + if (success) done(); + else done("Submitted times were not saved. Actual submissions: " + JSON.stringify(rows)); + } else { + done("Status code was " + res.statusCode); + } + }); + }).timeout(5000); + + it('Should reject multiple times if total is over 80% of video (JSON method)', (done: Done) => { + request.post(getbaseURL() + + "/api/postVideoSponsorTimes", { + json: { + userID: "test", + videoID: "n9rIGdXnSJc", + segments: [{ + segment: [0, 2000], + category: "interaction", + }, { + segment: [3000, 4000], + category: "sponsor", + }, { + segment: [1500, 2750], + category: "sponsor", + }, { + segment: [4050, 4750], + category: "intro", + }], + }, + }, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 400) { + let rows = db.prepare('all', "SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ? and votes > -1", ["n9rIGdXnSJc"]); + let success = true; + if (rows.length === 4) { + for (const row of rows) { + if ((row.startTime === 0 || row.endTime === 2000 || row.category === "interaction") || + (row.startTime === 3000 || row.endTime === 4000 || row.category === "sponsor") || + (row.startTime === 1500 || row.endTime === 2750 || row.category === "sponsor") || + (row.startTime === 4050 || row.endTime === 4750 || row.category === "intro")) { + success = false; + break; + } + } + } + + if (success) done(); + else + done("Submitted times were not saved. Actual submissions: " + JSON.stringify(rows)); + } else { + done("Status code was " + res.statusCode); + } + }); + }).timeout(5000); + + it('Should reject multiple times if total is over 80% of video including previosuly submitted times(JSON method)', (done: Done) => { + request.post(getbaseURL() + + "/api/postVideoSponsorTimes", { + json: { + userID: "test", + videoID: "80percent_video", + segments: [{ + segment: [2000, 4000], + category: "sponsor", + }, { + segment: [1500, 2750], + category: "sponsor", + }, { + segment: [4050, 4750], + category: "sponsor", + }], + }, + }, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 400) { + let rows = db.prepare('all', "SELECT startTime, endTime, category FROM sponsorTimes WHERE videoID = ? and votes > -1", ["80percent_video"]); + let success = true && rows.length == 2; + for (const row of rows) { + if ((row.startTime === 2000 || row.endTime === 4000 || row.category === "sponsor") || + (row.startTime === 1500 || row.endTime === 2750 || row.category === "sponsor") || + (row.startTime === 4050 || row.endTime === 4750 || row.category === "sponsor")) { + success = false; + break; + } + } + if (success) done(); + else + done("Submitted times were not saved. Actual submissions: " + JSON.stringify(rows)); + } else { + done("Status code was " + res.statusCode); + } + }); + }).timeout(5000); + + it('Should be accepted if a non-sponsor is less than 1 second', (done: Done) => { + request.post(getbaseURL() + + "/api/skipSegments?videoID=qqwerty&startTime=30&endTime=30.5&userID=testing&category=intro", null, + (err, res, body) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode === 200) done(); // pass + else done("non 200 status code: " + res.statusCode + " (" + body + ")"); + }); + }); + + it('Should be rejected if a sponsor is less than 1 second', (done: Done) => { + request.post(getbaseURL() + + "/api/skipSegments?videoID=qqwerty&startTime=30&endTime=30.5&userID=testing", null, + (err, res, body) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode === 400) done(); // pass + else done("non 403 status code: " + res.statusCode + " (" + body + ")"); + }); + }); + + it('Should be rejected if over 80% of the video', (done: Done) => { + request.get(getbaseURL() + + "/api/postVideoSponsorTimes?videoID=qqwerty&startTime=30&endTime=1000000&userID=testing", null, + (err, res, body) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode === 403) done(); // pass + else done("non 403 status code: " + res.statusCode + " (" + body + ")"); + }); + }); + + it("Should be rejected if NB's predicted probability is <70%.", (done: Done) => { + request.get(getbaseURL() + + "/api/postVideoSponsorTimes?videoID=LevkAjUE6d4&startTime=40&endTime=60&userID=testing", null, + (err, res, body) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode === 200) done(); // pass + else done("non 200 status code: " + res.statusCode + " (" + body + ")"); + }); + }); + + it('Should be rejected if user has to many active warnings', (done: Done) => { + request.post(getbaseURL() + + "/api/postVideoSponsorTimes", { + json: { + userID: "warn-user01", + videoID: "dQw4w9WgXcF", + segments: [{ + segment: [0, 10], + category: "sponsor", + }], + }, + }, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 403) { + done(); // success + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should be accepted if user has some active warnings', (done: Done) => { + request.post(getbaseURL() + + "/api/postVideoSponsorTimes", { + json: { + userID: "warn-user02", + videoID: "dQw4w9WgXcF", + segments: [{ + segment: [50, 60], + category: "sponsor", + }], + }, + }, + (err, res, body) => { + if (err) done(err); + else if (res.statusCode === 200) { + done(); // success + } else { + done("Status code was " + res.statusCode + " " + body); + } + }); + }); + + it('Should be allowed if youtube thinks duration is 0', (done: Done) => { + request.get(getbaseURL() + + "/api/postVideoSponsorTimes?videoID=noDuration&startTime=30&endTime=10000&userID=testing", null, + (err, res, body) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode === 200) done(); // pass + else done("non 200 status code: " + res.statusCode + " (" + body + ")"); + }); + }); + + it('Should be rejected if not a valid videoID', (done: Done) => { + request.get(getbaseURL() + + "/api/postVideoSponsorTimes?videoID=knownWrongID&startTime=30&endTime=1000000&userID=testing", null, + (err, res, body) => { + if (err) done("Couldn't call endpoint"); + else if (res.statusCode === 403) done(); // pass + else done("non 403 status code: " + res.statusCode + " (" + body + ")"); + }); + }); + + it('Should return 400 for missing params (Params method)', (done: Done) => { + request.post(getbaseURL() + + "/api/postVideoSponsorTimes?startTime=9&endTime=10&userID=test", null, + (err, res) => { + if (err) done(true); + if (res.statusCode === 400) done(); + else done(true); + }); + }); + + it('Should return 400 for missing params (JSON method) 1', (done: Done) => { + request.post(getbaseURL() + + "/api/postVideoSponsorTimes", { + json: { + userID: "test", + segments: [{ + segment: [9, 10], + category: "sponsor", + }, { + segment: [31, 60], + category: "intro", + }], + }, + }, + (err, res) => { + if (err) done(true); + else if (res.statusCode === 400) done(); + else done(true); + }); + }); + it('Should return 400 for missing params (JSON method) 2', (done: Done) => { + request.post(getbaseURL() + + "/api/postVideoSponsorTimes", { + json: { + userID: "test", + videoID: "dQw4w9WgXcQ", + }, + }, + (err, res) => { + if (err) done(true); + else if (res.statusCode === 400) done(); + else done(true); + }); + }); + it('Should return 400 for missing params (JSON method) 3', (done: Done) => { + request.post(getbaseURL() + + "/api/postVideoSponsorTimes", { + json: { + userID: "test", + videoID: "dQw4w9WgXcQ", + segments: [{ + segment: [0], + category: "sponsor", + }, { + segment: [31, 60], + category: "intro", + }], + }, + }, + (err, res) => { + if (err) done(true); + else if (res.statusCode === 400) done(); + else done(true); + }); + }); + it('Should return 400 for missing params (JSON method) 4', (done: Done) => { + request.post(getbaseURL() + + "/api/postVideoSponsorTimes", { + json: { + userID: "test", + videoID: "dQw4w9WgXcQ", + segments: [{ + segment: [9, 10], + }, { + segment: [31, 60], + category: "intro", + }], + }, + }, + (err, res) => { + if (err) done(true); + else if (res.statusCode === 400) done(); + else done(true); + }); + }); + it('Should return 400 for missing params (JSON method) 5', (done: Done) => { + request.post(getbaseURL() + + "/api/postVideoSponsorTimes", { + json: { + userID: "test", + videoID: "dQw4w9WgXcQ", + }, + }, + (err, res) => { + if (err) done(true); + else if (res.statusCode === 400) done(); + else done(true); + }); + }); +}); diff --git a/test/cases/postWarning.js b/test/cases/postWarning.js deleted file mode 100644 index e5e8586..0000000 --- a/test/cases/postWarning.js +++ /dev/null @@ -1,47 +0,0 @@ -var request = require('request'); -var utils = require('../utils.js'); -var db = require('../../src/databases/databases.js').db; -var getHash = require('../../src/utils/getHash.js'); - -describe('postWarning', () => { - before(() => { - db.exec("INSERT INTO vipUsers (userID) VALUES ('" + getHash("warning-vip") + "')"); - }); - - it('Should be able to create warning if vip (exp 200)', (done) => { - let json = { - issuerUserID: 'warning-vip', - userID: 'warning-0' - }; - - request.post(utils.getbaseURL() - + "/api/warnUser", {json}, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - done(); - } else { - console.log(body); - done("Status code was " + res.statusCode); - } - }); - }); - it('Should not be able to create warning if vip (exp 403)', (done) => { - let json = { - issuerUserID: 'warning-not-vip', - userID: 'warning-1' - }; - - request.post(utils.getbaseURL() - + "/api/warnUser", {json}, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 403) { - done(); - } else { - console.log(body); - done("Status code was " + res.statusCode); - } - }); - }); -}); diff --git a/test/cases/postWarning.ts b/test/cases/postWarning.ts new file mode 100644 index 0000000..97b13e4 --- /dev/null +++ b/test/cases/postWarning.ts @@ -0,0 +1,47 @@ +import request from 'request'; +import {Done, getbaseURL} from '../utils'; +import {db} from '../../src/databases/databases'; +import {getHash} from '../../src/utils/getHash'; + +describe('postWarning', () => { + before(() => { + db.exec("INSERT INTO vipUsers (userID) VALUES ('" + getHash("warning-vip") + "')"); + }); + + it('Should be able to create warning if vip (exp 200)', (done: Done) => { + let json = { + issuerUserID: 'warning-vip', + userID: 'warning-0', + }; + + request.post(getbaseURL() + + "/api/warnUser", {json}, + (err, res, body) => { + if (err) done(err); + else if (res.statusCode === 200) { + done(); + } else { + console.log(body); + done("Status code was " + res.statusCode); + } + }); + }); + it('Should not be able to create warning if vip (exp 403)', (done: Done) => { + let json = { + issuerUserID: 'warning-not-vip', + userID: 'warning-1', + }; + + request.post(getbaseURL() + + "/api/warnUser", {json}, + (err, res, body) => { + if (err) done(err); + else if (res.statusCode === 403) { + done(); + } else { + console.log(body); + done("Status code was " + res.statusCode); + } + }); + }); +}); diff --git a/test/cases/segmentShift.js b/test/cases/segmentShift.js deleted file mode 100644 index e1011e2..0000000 --- a/test/cases/segmentShift.js +++ /dev/null @@ -1,273 +0,0 @@ -const request = require('request'); -const utils = require('../utils.js'); -const { db } = require('../../src/databases/databases.js'); -const getHash = require('../../src/utils/getHash.js'); - -function dbSponsorTimesAdd(db, videoID, startTime, endTime, UUID, category) { - const votes = 0, - userID = 0, - timeSubmitted = 0, - views = 0, - shadowHidden = 0, - hashedVideoID = `hash_${UUID}`; - db.exec(`INSERT INTO - sponsorTimes (videoID, startTime, endTime, votes, UUID, - userID, timeSubmitted, views, category, shadowHidden, hashedVideoID) - VALUES - ('${videoID}', ${startTime}, ${endTime}, ${votes}, '${UUID}', - '${userID}', ${timeSubmitted}, ${views}, '${category}', ${shadowHidden}, '${hashedVideoID}') - `); -} - -function dbSponsorTimesSetByUUID(db, UUID, startTime, endTime) { - db.prepare('run', `UPDATE sponsorTimes SET startTime = ?, endTime = ? WHERE UUID = ?`, [startTime, endTime, UUID]); -} - -function dbSponsorTimesCompareExpect(db, expect) { - for (let i=0, len=expect.length; i { - if (err) return done(err); - return done(res.statusCode === 403 ? undefined : res.statusCode); - }); - }); - - it('Shift is outside segments', function(done) { - request.post(`${baseURL}/api/segmentShift`, { - json: { - videoID: 'vsegshift01', - userID: privateVipUserID, - startTime: 20, - endTime: 30, - } - }, (err, res, body) => { - if (err) return done(err); - if (res.statusCode !== 200) return done(`Status code was ${res.statusCode}`); - const expect = [ - { - UUID: 'vsegshifttest01uuid01', - startTime: 0, - endTime: 10, - }, - { - UUID: 'vsegshifttest01uuid02', - startTime: 50, - endTime: 80, - }, - { - UUID: 'vsegshifttest01uuid03', - startTime: 30, - endTime: 35, - }, - { - UUID: 'vsegshifttest01uuid04', - startTime: 110, - endTime: 130, - }, - ]; - done(dbSponsorTimesCompareExpect(db, expect)); - }); - }); - - it('Shift is inside segment', function(done) { - request.post(`${baseURL}/api/segmentShift`, { - json: { - videoID: 'vsegshift01', - userID: privateVipUserID, - startTime: 65, - endTime: 75, - } - }, (err, res, body) => { - if (err) return done(err); - if (res.statusCode !== 200) return done(`Status code was ${res.statusCode}`); - const expect = [ - { - UUID: 'vsegshifttest01uuid01', - startTime: 0, - endTime: 10, - }, - { - UUID: 'vsegshifttest01uuid02', - startTime: 60, - endTime: 80, - }, - { - UUID: 'vsegshifttest01uuid03', - startTime: 40, - endTime: 45, - }, - { - UUID: 'vsegshifttest01uuid04', - startTime: 110, - endTime: 130, - }, - ]; - done(dbSponsorTimesCompareExpect(db, expect)); - }); - }); - - it('Shift is overlaping startTime of segment', function(done) { - request.post(`${baseURL}/api/segmentShift`, { - json: { - videoID: 'vsegshift01', - userID: privateVipUserID, - startTime: 32, - endTime: 42, - } - }, (err, res, body) => { - if (err) return done(err); - if (res.statusCode !== 200) return done(`Status code was ${res.statusCode}`); - const expect = [ - { - UUID: 'vsegshifttest01uuid01', - startTime: 0, - endTime: 10, - }, - { - UUID: 'vsegshifttest01uuid02', - startTime: 50, - endTime: 80, - }, - { - UUID: 'vsegshifttest01uuid03', - startTime: 32, - endTime: 35, - }, - { - UUID: 'vsegshifttest01uuid04', - startTime: 110, - endTime: 130, - }, - ]; - done(dbSponsorTimesCompareExpect(db, expect)); - }); - }); - - it('Shift is overlaping endTime of segment', function(done) { - request.post(`${baseURL}/api/segmentShift`, { - json: { - videoID: 'vsegshift01', - userID: privateVipUserID, - startTime: 85, - endTime: 95, - } - }, (err, res, body) => { - if (err) return done(err); - if (res.statusCode !== 200) return done(`Status code was ${res.statusCode}`); - const expect = [ - { - UUID: 'vsegshifttest01uuid01', - startTime: 0, - endTime: 10, - }, - { - UUID: 'vsegshifttest01uuid02', - startTime: 60, - endTime: 85, - }, - { - UUID: 'vsegshifttest01uuid03', - startTime: 40, - endTime: 45, - }, - { - UUID: 'vsegshifttest01uuid04', - startTime: 110, - endTime: 130, - }, - ]; - done(dbSponsorTimesCompareExpect(db, expect)); - }); - }); - - it('Shift is overlaping segment', function(done) { - request.post(`${baseURL}/api/segmentShift`, { - json: { - videoID: 'vsegshift01', - userID: privateVipUserID, - startTime: 35, - endTime: 55, - } - }, (err, res, body) => { - if (err) return done(err); - if (res.statusCode !== 200) return done(`Status code was ${res.statusCode}`); - const expect = [ - { - UUID: 'vsegshifttest01uuid01', - startTime: 0, - endTime: 10, - }, - { - UUID: 'vsegshifttest01uuid02', - startTime: 40, - endTime: 70, - }, - { - UUID: 'vsegshifttest01uuid03', - startTime: 40, - endTime: 45, - removed: true, - }, - { - UUID: 'vsegshifttest01uuid04', - startTime: 100, - endTime: 120, - }, - ]; - done(dbSponsorTimesCompareExpect(db, expect)); - }); - }); - - -}); diff --git a/test/cases/segmentShift.ts b/test/cases/segmentShift.ts new file mode 100644 index 0000000..3910cd6 --- /dev/null +++ b/test/cases/segmentShift.ts @@ -0,0 +1,273 @@ +import request from 'request'; +import {Done, getbaseURL} from '../utils'; +import {db} from '../../src/databases/databases'; +import {getHash} from '../../src/utils/getHash'; +import {IDatabase} from '../../src/databases/IDatabase'; + +function dbSponsorTimesAdd(db: IDatabase, videoID: string, startTime: number, endTime: number, UUID: string, category: string) { + const votes = 0, + userID = 0, + timeSubmitted = 0, + views = 0, + shadowHidden = 0, + hashedVideoID = `hash_${UUID}`; + db.exec(`INSERT INTO + sponsorTimes (videoID, startTime, endTime, votes, UUID, + userID, timeSubmitted, views, category, shadowHidden, hashedVideoID) + VALUES + ('${videoID}', ${startTime}, ${endTime}, ${votes}, '${UUID}', + '${userID}', ${timeSubmitted}, ${views}, '${category}', ${shadowHidden}, '${hashedVideoID}') + `); +} + +function dbSponsorTimesSetByUUID(db: IDatabase, UUID: string, startTime: number, endTime: number) { + db.prepare('run', `UPDATE sponsorTimes SET startTime = ?, endTime = ? WHERE UUID = ?`, [startTime, endTime, UUID]); +} + +function dbSponsorTimesCompareExpect(db: IDatabase, expect: any) { + for (let i = 0, len = expect.length; i < len; i++) { + const expectSeg = expect[i]; + let seg = db.prepare('get', "SELECT startTime, endTime FROM sponsorTimes WHERE UUID = ?", [expectSeg.UUID]); + if ('removed' in expect) { + if (expect.removed === true && seg.votes === -2) { + return; + } else { + return `${expectSeg.UUID} doesnt got removed`; + } + } + if (seg.startTime !== expectSeg.startTime) { + return `${expectSeg.UUID} startTime is incorrect. seg.startTime is ${seg.startTime} expected ${expectSeg.startTime}`; + } + if (seg.endTime !== expectSeg.endTime) { + return `${expectSeg.UUID} endTime is incorrect. seg.endTime is ${seg.endTime} expected ${expectSeg.endTime}`; + } + } + return; +} + +describe('segmentShift', function () { + const privateVipUserID = 'VIPUser-segmentShift'; + const vipUserID = getHash(privateVipUserID); + const baseURL = getbaseURL(); + + before(function (done: Done) { + // startTime and endTime get set in beforeEach for consistency + dbSponsorTimesAdd(db, 'vsegshift01', 0, 0, 'vsegshifttest01uuid01', 'intro'); + dbSponsorTimesAdd(db, 'vsegshift01', 0, 0, 'vsegshifttest01uuid02', 'sponsor'); + dbSponsorTimesAdd(db, 'vsegshift01', 0, 0, 'vsegshifttest01uuid03', 'interaction'); + dbSponsorTimesAdd(db, 'vsegshift01', 0, 0, 'vsegshifttest01uuid04', 'outro'); + db.exec(`INSERT INTO vipUsers (userID) VALUES ('${vipUserID}')`); + done(); + }); + + beforeEach(function (done: Done) { + // resetting startTime and endTime to reuse them + dbSponsorTimesSetByUUID(db, 'vsegshifttest01uuid01', 0, 10); + dbSponsorTimesSetByUUID(db, 'vsegshifttest01uuid02', 60, 90); + dbSponsorTimesSetByUUID(db, 'vsegshifttest01uuid03', 40, 45); + dbSponsorTimesSetByUUID(db, 'vsegshifttest01uuid04', 120, 140); + done(); + }); + + it('Reject none VIP user', function (done: Done) { + request.post(`${baseURL}/api/segmentShift`, { + json: { + videoID: 'vsegshift01', + userID: 'segshift_randomuser001', + startTime: 20, + endTime: 30, + }, + }, (err, res) => { + if (err) return done(err); + return done(res.statusCode === 403 ? undefined : res.statusCode); + }); + }); + + it('Shift is outside segments', function (done: Done) { + request.post(`${baseURL}/api/segmentShift`, { + json: { + videoID: 'vsegshift01', + userID: privateVipUserID, + startTime: 20, + endTime: 30, + }, + }, (err, res) => { + if (err) return done(err); + if (res.statusCode !== 200) return done(`Status code was ${res.statusCode}`); + const expect = [ + { + UUID: 'vsegshifttest01uuid01', + startTime: 0, + endTime: 10, + }, + { + UUID: 'vsegshifttest01uuid02', + startTime: 50, + endTime: 80, + }, + { + UUID: 'vsegshifttest01uuid03', + startTime: 30, + endTime: 35, + }, + { + UUID: 'vsegshifttest01uuid04', + startTime: 110, + endTime: 130, + }, + ]; + done(dbSponsorTimesCompareExpect(db, expect)); + }); + }); + + it('Shift is inside segment', function (done: Done) { + request.post(`${baseURL}/api/segmentShift`, { + json: { + videoID: 'vsegshift01', + userID: privateVipUserID, + startTime: 65, + endTime: 75, + }, + }, (err, res) => { + if (err) return done(err); + if (res.statusCode !== 200) return done(`Status code was ${res.statusCode}`); + const expect = [ + { + UUID: 'vsegshifttest01uuid01', + startTime: 0, + endTime: 10, + }, + { + UUID: 'vsegshifttest01uuid02', + startTime: 60, + endTime: 80, + }, + { + UUID: 'vsegshifttest01uuid03', + startTime: 40, + endTime: 45, + }, + { + UUID: 'vsegshifttest01uuid04', + startTime: 110, + endTime: 130, + }, + ]; + done(dbSponsorTimesCompareExpect(db, expect)); + }); + }); + + it('Shift is overlaping startTime of segment', function (done: Done) { + request.post(`${baseURL}/api/segmentShift`, { + json: { + videoID: 'vsegshift01', + userID: privateVipUserID, + startTime: 32, + endTime: 42, + }, + }, (err, res) => { + if (err) return done(err); + if (res.statusCode !== 200) return done(`Status code was ${res.statusCode}`); + const expect = [ + { + UUID: 'vsegshifttest01uuid01', + startTime: 0, + endTime: 10, + }, + { + UUID: 'vsegshifttest01uuid02', + startTime: 50, + endTime: 80, + }, + { + UUID: 'vsegshifttest01uuid03', + startTime: 32, + endTime: 35, + }, + { + UUID: 'vsegshifttest01uuid04', + startTime: 110, + endTime: 130, + }, + ]; + done(dbSponsorTimesCompareExpect(db, expect)); + }); + }); + + it('Shift is overlaping endTime of segment', function (done: Done) { + request.post(`${baseURL}/api/segmentShift`, { + json: { + videoID: 'vsegshift01', + userID: privateVipUserID, + startTime: 85, + endTime: 95, + }, + }, (err, res) => { + if (err) return done(err); + if (res.statusCode !== 200) return done(`Status code was ${res.statusCode}`); + const expect = [ + { + UUID: 'vsegshifttest01uuid01', + startTime: 0, + endTime: 10, + }, + { + UUID: 'vsegshifttest01uuid02', + startTime: 60, + endTime: 85, + }, + { + UUID: 'vsegshifttest01uuid03', + startTime: 40, + endTime: 45, + }, + { + UUID: 'vsegshifttest01uuid04', + startTime: 110, + endTime: 130, + }, + ]; + done(dbSponsorTimesCompareExpect(db, expect)); + }); + }); + + it('Shift is overlaping segment', function (done: Done) { + request.post(`${baseURL}/api/segmentShift`, { + json: { + videoID: 'vsegshift01', + userID: privateVipUserID, + startTime: 35, + endTime: 55, + }, + }, (err, res) => { + if (err) return done(err); + if (res.statusCode !== 200) return done(`Status code was ${res.statusCode}`); + const expect = [ + { + UUID: 'vsegshifttest01uuid01', + startTime: 0, + endTime: 10, + }, + { + UUID: 'vsegshifttest01uuid02', + startTime: 40, + endTime: 70, + }, + { + UUID: 'vsegshifttest01uuid03', + startTime: 40, + endTime: 45, + removed: true, + }, + { + UUID: 'vsegshifttest01uuid04', + startTime: 100, + endTime: 120, + }, + ]; + done(dbSponsorTimesCompareExpect(db, expect)); + }); + }); + + +}); diff --git a/test/cases/voteOnSponsorTime.js b/test/cases/voteOnSponsorTime.js deleted file mode 100644 index 084d0f9..0000000 --- a/test/cases/voteOnSponsorTime.js +++ /dev/null @@ -1,367 +0,0 @@ -const request = require('request'); -const config = require('../../src/config.js'); -const { db, privateDB } = require('../../src/databases/databases.js'); -const utils = require('../utils.js'); -const getHash = require('../../src/utils/getHash.js'); - -describe('voteOnSponsorTime', () => { - before(() => { - const now = Date.now(); - const warnVip01Hash = getHash("warn-vip01"); - const warnUser01Hash = getHash("warn-voteuser01"); - const warnUser02Hash = getHash("warn-voteuser02"); - const MILLISECONDS_IN_HOUR = 3600000; - const warningExpireTime = MILLISECONDS_IN_HOUR * config.hoursAfterWarningExpires; - let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden, hashedVideoID) VALUES"; - const startOfWarningQuery = 'INSERT INTO warnings (userID, issueTime, issuerUserID) VALUES'; - - db.exec(startOfQuery + "('vote-testtesttest', 1, 11, 2, 'vote-uuid-0', 'testman', 0, 50, 'sponsor', 0, '" + getHash('vote-testtesttest', 1) + "')"); - db.exec(startOfQuery + "('vote-testtesttest2', 1, 11, 2, 'vote-uuid-1', 'testman', 0, 50, 'sponsor', 0, '" + getHash('vote-testtesttest2', 1) + "')"); - db.exec(startOfQuery + "('vote-testtesttest2', 1, 11, 10, 'vote-uuid-1.5', 'testman', 0, 50, 'outro', 0, '" + getHash('vote-testtesttest2', 1) + "')"); - db.exec(startOfQuery + "('vote-testtesttest2', 1, 11, 10, 'vote-uuid-1.6', 'testman', 0, 50, 'interaction', 0, '" + getHash('vote-testtesttest2', 1) + "')"); - db.exec(startOfQuery + "('vote-testtesttest3', 20, 33, 10, 'vote-uuid-2', 'testman', 0, 50, 'intro', 0, '" + getHash('vote-testtesttest3', 1) + "')"); - db.exec(startOfQuery + "('vote-testtesttest,test', 1, 11, 100, 'vote-uuid-3', 'testman', 0, 50, 'sponsor', 0, '" + getHash('vote-testtesttest,test', 1) + "')"); - db.exec(startOfQuery + "('vote-test3', 1, 11, 2, 'vote-uuid-4', 'testman', 0, 50, 'sponsor', 0, '" + getHash('vote-test3', 1) + "')"); - db.exec(startOfQuery + "('vote-test3', 7, 22, -3, 'vote-uuid-5', 'testman', 0, 50, 'intro', 0, '" + getHash('vote-test3', 1) + "')"); - db.exec(startOfQuery + "('vote-multiple', 1, 11, 2, 'vote-uuid-6', 'testman', 0, 50, 'intro', 0, '" + getHash('vote-multiple', 1) + "')"); - db.exec(startOfQuery + "('vote-multiple', 20, 33, 2, 'vote-uuid-7', 'testman', 0, 50, 'intro', 0, '" + getHash('vote-multiple', 1) + "')"); - db.exec(startOfQuery + "('voter-submitter', 1, 11, 2, 'vote-uuid-8', '" + getHash("randomID") + "', 0, 50, 'sponsor', 0, '" + getHash('voter-submitter', 1) + "')"); - db.exec(startOfQuery + "('voter-submitter2', 1, 11, 2, 'vote-uuid-9', '" + getHash("randomID2") + "', 0, 50, 'sponsor', 0, '" + getHash('voter-submitter2', 1) + "')"); - db.exec(startOfQuery + "('voter-submitter2', 1, 11, 2, 'vote-uuid-10', '" + getHash("randomID3") + "', 0, 50, 'sponsor', 0, '" + getHash('voter-submitter2', 1) + "')"); - db.exec(startOfQuery + "('voter-submitter2', 1, 11, 2, 'vote-uuid-11', '" + getHash("randomID4") + "', 0, 50, 'sponsor', 0, '" + getHash('voter-submitter2', 1) + "')"); - db.exec(startOfQuery + "('own-submission-video', 1, 11, 500, 'own-submission-uuid', '"+ getHash('own-submission-id') +"', 0, 50, 'sponsor', 0, '" + getHash('own-submission-video', 1) + "')"); - db.exec(startOfQuery + "('not-own-submission-video', 1, 11, 500, 'not-own-submission-uuid', '"+ getHash('somebody-else-id') +"', 0, 50, 'sponsor', 0, '" + getHash('not-own-submission-video', 1) + "')"); - db.exec(startOfQuery + "('incorrect-category', 1, 11, 500, 'incorrect-category', '"+ getHash('somebody-else-id') +"', 0, 50, 'sponsor', 0, '" + getHash('incorrect-category', 1) + "')"); - db.exec(startOfQuery + "('incorrect-category-change', 1, 11, 500, 'incorrect-category-change', '"+ getHash('somebody-else-id') +"', 0, 50, 'sponsor', 0, '" + getHash('incorrect-category-change', 1) + "')"); - db.exec(startOfQuery + "('vote-testtesttest', 1, 11, 2, 'warnvote-uuid-0', 'testman', 0, 50, 'sponsor', 0, '" + getHash('vote-testtesttest', 1) + "')"); - - db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + now + "', '" + warnVip01Hash + "')"); - db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + (now-1000) + "', '" + warnVip01Hash + "')"); - db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + (now-2000) + "', '" + warnVip01Hash + "')"); - db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + (now-3601000) + "', '" + warnVip01Hash + "')"); - db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + now + "', '" + warnVip01Hash + "')"); - db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + now + "', '" + warnVip01Hash + "')"); - db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + (now-(warningExpireTime + 1000)) + "', '" + warnVip01Hash + "')"); - db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + (now-(warningExpireTime + 2000)) + "', '" + warnVip01Hash + "')"); - - - db.exec("INSERT INTO vipUsers (userID) VALUES ('" + getHash("VIPUser") + "')"); - privateDB.exec("INSERT INTO shadowBannedUsers (userID) VALUES ('" + getHash("randomID4") + "')"); - }); - - it('Should be able to upvote a segment', (done) => { - request.get(utils.getbaseURL() - + "/api/voteOnSponsorTime?userID=randomID&UUID=vote-uuid-0&type=1", null, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - let row = db.prepare('get', "SELECT votes FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-0"]); - if (row.votes === 3) { - done() - } else { - done("Vote did not succeed. Submission went from 2 votes to " + row.votes); - } - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - it('Should be able to downvote a segment', (done) => { - request.get(utils.getbaseURL() - + "/api/voteOnSponsorTime?userID=randomID2&UUID=vote-uuid-2&type=0", null, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - let row = db.prepare('get', "SELECT votes FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-2"]); - if (row.votes < 10) { - done() - } else { - done("Vote did not succeed. Submission went from 10 votes to " + row.votes); - } - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - it('Should not be able to downvote the same segment when voting from a different user on the same IP', (done) => { - request.get(utils.getbaseURL() - + "/api/voteOnSponsorTime?userID=randomID3&UUID=vote-uuid-2&type=0", null, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - let row = db.prepare('get', "SELECT votes FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-2"]); - if (row.votes === 9) { - done() - } else { - done("Vote did not fail. Submission went from 9 votes to " + row.votes); - } - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - it("Should not be able to downvote a segment if the user is shadow banned", (done) => { - request.get(utils.getbaseURL() - + "/api/voteOnSponsorTime?userID=randomID4&UUID=vote-uuid-1.6&type=0", null, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - let row = db.prepare('get', "SELECT votes FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-1.6"]); - if (row.votes === 10) { - done() - } else { - done("Vote did not fail. Submission went from 10 votes to " + row.votes); - } - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - it("Should not be able to upvote a segment if the user hasn't submitted yet", (done) => { - request.get(utils.getbaseURL() - + "/api/voteOnSponsorTime?userID=hasNotSubmittedID&UUID=vote-uuid-1&type=1", null, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - let row = db.prepare('get', "SELECT votes FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-1"]); - if (row.votes === 2) { - done() - } else { - done("Vote did not fail. Submission went from 2 votes to " + row.votes); - } - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - it("Should not be able to downvote a segment if the user hasn't submitted yet", (done) => { - request.get(utils.getbaseURL() - + "/api/voteOnSponsorTime?userID=hasNotSubmittedID&UUID=vote-uuid-1.5&type=0", null, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - let row = db.prepare('get', "SELECT votes FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-1.5"]); - if (row.votes === 10) { - done() - } else { - done("Vote did not fail. Submission went from 10 votes to " + row.votes); - } - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - it('VIP should be able to completely downvote a segment', (done) => { - request.get(utils.getbaseURL() - + "/api/voteOnSponsorTime?userID=VIPUser&UUID=vote-uuid-3&type=0", null, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - let row = db.prepare('get', "SELECT votes FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-3"]); - if (row.votes <= -2) { - done() - } else { - done("Vote did not succeed. Submission went from 100 votes to " + row.votes); - } - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - it('should be able to completely downvote your own segment', (done) => { - request.get(utils.getbaseURL() - + "/api/voteOnSponsorTime?userID=own-submission-id&UUID=own-submission-uuid&type=0", null, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - let row = db.prepare('get', "SELECT votes FROM sponsorTimes WHERE UUID = ?", ["own-submission-uuid"]); - if (row.votes <= -2) { - done() - } else { - done("Vote did not succeed. Submission went from 500 votes to " + row.votes); - } - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - it('should not be able to completely downvote somebody elses segment', (done) => { - request.get(utils.getbaseURL() - + "/api/voteOnSponsorTime?userID=randomID2&UUID=not-own-submission-uuid&type=0", null, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - let row = db.prepare('get', "SELECT votes FROM sponsorTimes WHERE UUID = ?", ["not-own-submission-uuid"]); - if (row.votes === 499) { - done() - } else { - done("Vote did not succeed. Submission went from 500 votes to " + row.votes); - } - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - it('Should be able to vote for a category and it should immediately change (for now)', (done) => { - request.get(utils.getbaseURL() - + "/api/voteOnSponsorTime?userID=randomID2&UUID=vote-uuid-4&category=intro", null, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - let row = db.prepare('get', "SELECT category FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-4"]); - if (row.category === "intro") { - done() - } else { - done("Vote did not succeed. Submission went from sponsor to " + row.category); - } - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - it('Should not able to change to an invalid category', (done) => { - request.get(utils.getbaseURL() - + "/api/voteOnSponsorTime?userID=randomID2&UUID=incorrect-category&category=fakecategory", null, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 400) { - let row = db.prepare('get', "SELECT category FROM sponsorTimes WHERE UUID = ?", ["incorrect-category"]); - if (row.category === "sponsor") { - done() - } else { - done("Vote did not succeed. Submission went from sponsor to " + row.category); - } - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - it('Should be able to change your vote for a category and it should immediately change (for now)', (done) => { - request.get(utils.getbaseURL() - + "/api/voteOnSponsorTime?userID=randomID2&UUID=vote-uuid-4&category=outro", null, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - let row = db.prepare('get', "SELECT category FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-4"]); - if (row.category === "outro") { - done() - } else { - done("Vote did not succeed. Submission went from intro to " + row.category); - } - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - - it('Should not be able to change your vote to an invalid category', (done) => { - const vote = (inputCat, assertCat, callback) => { - request.get(utils.getbaseURL() - + "/api/voteOnSponsorTime?userID=randomID2&UUID=incorrect-category-change&category="+inputCat, null, - (err) => { - if (err) done(err); - else{ - let row = db.prepare('get', "SELECT category FROM sponsorTimes WHERE UUID = ?", ["incorrect-category-change"]); - if (row.category === assertCat) { - callback(); - } else { - done("Vote did not succeed. Submission went from sponsor to " + row.category); - } - } - }); - }; - vote("sponsor", "sponsor", () => { - vote("fakeCategory", "sponsor", done); - }); - }); - - - it('VIP should be able to vote for a category and it should immediately change', (done) => { - request.get(utils.getbaseURL() - + "/api/voteOnSponsorTime?userID=VIPUser&UUID=vote-uuid-5&category=outro", null, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - let row = db.prepare('get', "SELECT category FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-5"]); - let row2 = db.prepare('get', "SELECT votes FROM categoryVotes WHERE UUID = ? and category = ?", ["vote-uuid-5", "outro"]); - if (row.category === "outro" && row2.votes === 500) { - done() - } else { - done("Vote did not succeed. Submission went from intro to " + row.category + ". Category votes are " + row2.votes + " and should be 500."); - } - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - it('Should not be able to category-vote on an invalid UUID submission', (done) => { - request.get(utils.getbaseURL() - + "/api/voteOnSponsorTime?userID=randomID3&UUID=invalid-uuid&category=intro", null, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 400) { - done(); - } else { - done("Status code was " + res.statusCode + " instead of 400."); - } - }); - }); - - it('Non-VIP should not be able to upvote "dead" submission', (done) => { - request.get(utils.getbaseURL() - + "/api/voteOnSponsorTime?userID=randomID2&UUID=vote-uuid-5&type=1", null, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 403) { - done(); - } else { - done("Status code was " + res.statusCode + " instead of 403"); - } - }); - }); - - it('VIP should be able to upvote "dead" submission', (done) => { - request.get(utils.getbaseURL() - + "/api/voteOnSponsorTime?userID=VIPUser&UUID=vote-uuid-5&type=1", null, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 200) { - let row = db.prepare('get', "SELECT votes FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-5"]); - if (row.votes > -3) { - done() - } else { - done("Vote did not succeed. Votes raised from -3 to " + row.votes); - } - } else { - done("Status code was " + res.statusCode); - } - }); - }); - - it('Should not be able to upvote a segment (Too many warning)', (done) => { - request.get(utils.getbaseURL() - + "/api/voteOnSponsorTime?userID=warn-voteuser01&UUID=warnvote-uuid-0&type=1", null, - (err, res, body) => { - if (err) done(err); - else if (res.statusCode === 403) { - done(); // success - } else { - done("Status code was " + res.statusCode); - } - }); - }); - -}); \ No newline at end of file diff --git a/test/cases/voteOnSponsorTime.ts b/test/cases/voteOnSponsorTime.ts new file mode 100644 index 0000000..329d0a1 --- /dev/null +++ b/test/cases/voteOnSponsorTime.ts @@ -0,0 +1,367 @@ +import request from 'request'; +import {config} from '../../src/config'; +import {db, privateDB} from '../../src/databases/databases'; +import {Done, getbaseURL} from '../utils'; +import {getHash} from '../../src/utils/getHash'; + +describe('voteOnSponsorTime', () => { + before(() => { + const now = Date.now(); + const warnVip01Hash = getHash("warn-vip01"); + const warnUser01Hash = getHash("warn-voteuser01"); + const warnUser02Hash = getHash("warn-voteuser02"); + const MILLISECONDS_IN_HOUR = 3600000; + const warningExpireTime = MILLISECONDS_IN_HOUR * config.hoursAfterWarningExpires; + let startOfQuery = "INSERT INTO sponsorTimes (videoID, startTime, endTime, votes, UUID, userID, timeSubmitted, views, category, shadowHidden, hashedVideoID) VALUES"; + const startOfWarningQuery = 'INSERT INTO warnings (userID, issueTime, issuerUserID) VALUES'; + + db.exec(startOfQuery + "('vote-testtesttest', 1, 11, 2, 'vote-uuid-0', 'testman', 0, 50, 'sponsor', 0, '" + getHash('vote-testtesttest', 1) + "')"); + db.exec(startOfQuery + "('vote-testtesttest2', 1, 11, 2, 'vote-uuid-1', 'testman', 0, 50, 'sponsor', 0, '" + getHash('vote-testtesttest2', 1) + "')"); + db.exec(startOfQuery + "('vote-testtesttest2', 1, 11, 10, 'vote-uuid-1.5', 'testman', 0, 50, 'outro', 0, '" + getHash('vote-testtesttest2', 1) + "')"); + db.exec(startOfQuery + "('vote-testtesttest2', 1, 11, 10, 'vote-uuid-1.6', 'testman', 0, 50, 'interaction', 0, '" + getHash('vote-testtesttest2', 1) + "')"); + db.exec(startOfQuery + "('vote-testtesttest3', 20, 33, 10, 'vote-uuid-2', 'testman', 0, 50, 'intro', 0, '" + getHash('vote-testtesttest3', 1) + "')"); + db.exec(startOfQuery + "('vote-testtesttest,test', 1, 11, 100, 'vote-uuid-3', 'testman', 0, 50, 'sponsor', 0, '" + getHash('vote-testtesttest,test', 1) + "')"); + db.exec(startOfQuery + "('vote-test3', 1, 11, 2, 'vote-uuid-4', 'testman', 0, 50, 'sponsor', 0, '" + getHash('vote-test3', 1) + "')"); + db.exec(startOfQuery + "('vote-test3', 7, 22, -3, 'vote-uuid-5', 'testman', 0, 50, 'intro', 0, '" + getHash('vote-test3', 1) + "')"); + db.exec(startOfQuery + "('vote-multiple', 1, 11, 2, 'vote-uuid-6', 'testman', 0, 50, 'intro', 0, '" + getHash('vote-multiple', 1) + "')"); + db.exec(startOfQuery + "('vote-multiple', 20, 33, 2, 'vote-uuid-7', 'testman', 0, 50, 'intro', 0, '" + getHash('vote-multiple', 1) + "')"); + db.exec(startOfQuery + "('voter-submitter', 1, 11, 2, 'vote-uuid-8', '" + getHash("randomID") + "', 0, 50, 'sponsor', 0, '" + getHash('voter-submitter', 1) + "')"); + db.exec(startOfQuery + "('voter-submitter2', 1, 11, 2, 'vote-uuid-9', '" + getHash("randomID2") + "', 0, 50, 'sponsor', 0, '" + getHash('voter-submitter2', 1) + "')"); + db.exec(startOfQuery + "('voter-submitter2', 1, 11, 2, 'vote-uuid-10', '" + getHash("randomID3") + "', 0, 50, 'sponsor', 0, '" + getHash('voter-submitter2', 1) + "')"); + db.exec(startOfQuery + "('voter-submitter2', 1, 11, 2, 'vote-uuid-11', '" + getHash("randomID4") + "', 0, 50, 'sponsor', 0, '" + getHash('voter-submitter2', 1) + "')"); + db.exec(startOfQuery + "('own-submission-video', 1, 11, 500, 'own-submission-uuid', '" + getHash('own-submission-id') + "', 0, 50, 'sponsor', 0, '" + getHash('own-submission-video', 1) + "')"); + db.exec(startOfQuery + "('not-own-submission-video', 1, 11, 500, 'not-own-submission-uuid', '" + getHash('somebody-else-id') + "', 0, 50, 'sponsor', 0, '" + getHash('not-own-submission-video', 1) + "')"); + db.exec(startOfQuery + "('incorrect-category', 1, 11, 500, 'incorrect-category', '" + getHash('somebody-else-id') + "', 0, 50, 'sponsor', 0, '" + getHash('incorrect-category', 1) + "')"); + db.exec(startOfQuery + "('incorrect-category-change', 1, 11, 500, 'incorrect-category-change', '" + getHash('somebody-else-id') + "', 0, 50, 'sponsor', 0, '" + getHash('incorrect-category-change', 1) + "')"); + db.exec(startOfQuery + "('vote-testtesttest', 1, 11, 2, 'warnvote-uuid-0', 'testman', 0, 50, 'sponsor', 0, '" + getHash('vote-testtesttest', 1) + "')"); + + db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + now + "', '" + warnVip01Hash + "')"); + db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + (now - 1000) + "', '" + warnVip01Hash + "')"); + db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + (now - 2000) + "', '" + warnVip01Hash + "')"); + db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + (now - 3601000) + "', '" + warnVip01Hash + "')"); + db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + now + "', '" + warnVip01Hash + "')"); + db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + now + "', '" + warnVip01Hash + "')"); + db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + (now - (warningExpireTime + 1000)) + "', '" + warnVip01Hash + "')"); + db.exec(startOfWarningQuery + "('" + warnUser02Hash + "', '" + (now - (warningExpireTime + 2000)) + "', '" + warnVip01Hash + "')"); + + + db.exec("INSERT INTO vipUsers (userID) VALUES ('" + getHash("VIPUser") + "')"); + privateDB.exec("INSERT INTO shadowBannedUsers (userID) VALUES ('" + getHash("randomID4") + "')"); + }); + + it('Should be able to upvote a segment', (done: Done) => { + request.get(getbaseURL() + + "/api/voteOnSponsorTime?userID=randomID&UUID=vote-uuid-0&type=1", null, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 200) { + let row = db.prepare('get', "SELECT votes FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-0"]); + if (row.votes === 3) { + done(); + } else { + done("Vote did not succeed. Submission went from 2 votes to " + row.votes); + } + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should be able to downvote a segment', (done: Done) => { + request.get(getbaseURL() + + "/api/voteOnSponsorTime?userID=randomID2&UUID=vote-uuid-2&type=0", null, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 200) { + let row = db.prepare('get', "SELECT votes FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-2"]); + if (row.votes < 10) { + done(); + } else { + done("Vote did not succeed. Submission went from 10 votes to " + row.votes); + } + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should not be able to downvote the same segment when voting from a different user on the same IP', (done: Done) => { + request.get(getbaseURL() + + "/api/voteOnSponsorTime?userID=randomID3&UUID=vote-uuid-2&type=0", null, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 200) { + let row = db.prepare('get', "SELECT votes FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-2"]); + if (row.votes === 9) { + done(); + } else { + done("Vote did not fail. Submission went from 9 votes to " + row.votes); + } + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it("Should not be able to downvote a segment if the user is shadow banned", (done: Done) => { + request.get(getbaseURL() + + "/api/voteOnSponsorTime?userID=randomID4&UUID=vote-uuid-1.6&type=0", null, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 200) { + let row = db.prepare('get', "SELECT votes FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-1.6"]); + if (row.votes === 10) { + done(); + } else { + done("Vote did not fail. Submission went from 10 votes to " + row.votes); + } + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it("Should not be able to upvote a segment if the user hasn't submitted yet", (done: Done) => { + request.get(getbaseURL() + + "/api/voteOnSponsorTime?userID=hasNotSubmittedID&UUID=vote-uuid-1&type=1", null, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 200) { + let row = db.prepare('get', "SELECT votes FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-1"]); + if (row.votes === 2) { + done(); + } else { + done("Vote did not fail. Submission went from 2 votes to " + row.votes); + } + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it("Should not be able to downvote a segment if the user hasn't submitted yet", (done: Done) => { + request.get(getbaseURL() + + "/api/voteOnSponsorTime?userID=hasNotSubmittedID&UUID=vote-uuid-1.5&type=0", null, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 200) { + let row = db.prepare('get', "SELECT votes FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-1.5"]); + if (row.votes === 10) { + done(); + } else { + done("Vote did not fail. Submission went from 10 votes to " + row.votes); + } + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('VIP should be able to completely downvote a segment', (done: Done) => { + request.get(getbaseURL() + + "/api/voteOnSponsorTime?userID=VIPUser&UUID=vote-uuid-3&type=0", null, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 200) { + let row = db.prepare('get', "SELECT votes FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-3"]); + if (row.votes <= -2) { + done(); + } else { + done("Vote did not succeed. Submission went from 100 votes to " + row.votes); + } + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('should be able to completely downvote your own segment', (done: Done) => { + request.get(getbaseURL() + + "/api/voteOnSponsorTime?userID=own-submission-id&UUID=own-submission-uuid&type=0", null, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 200) { + let row = db.prepare('get', "SELECT votes FROM sponsorTimes WHERE UUID = ?", ["own-submission-uuid"]); + if (row.votes <= -2) { + done(); + } else { + done("Vote did not succeed. Submission went from 500 votes to " + row.votes); + } + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('should not be able to completely downvote somebody elses segment', (done: Done) => { + request.get(getbaseURL() + + "/api/voteOnSponsorTime?userID=randomID2&UUID=not-own-submission-uuid&type=0", null, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 200) { + let row = db.prepare('get', "SELECT votes FROM sponsorTimes WHERE UUID = ?", ["not-own-submission-uuid"]); + if (row.votes === 499) { + done(); + } else { + done("Vote did not succeed. Submission went from 500 votes to " + row.votes); + } + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should be able to vote for a category and it should immediately change (for now)', (done: Done) => { + request.get(getbaseURL() + + "/api/voteOnSponsorTime?userID=randomID2&UUID=vote-uuid-4&category=intro", null, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 200) { + let row = db.prepare('get', "SELECT category FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-4"]); + if (row.category === "intro") { + done(); + } else { + done("Vote did not succeed. Submission went from sponsor to " + row.category); + } + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should not able to change to an invalid category', (done: Done) => { + request.get(getbaseURL() + + "/api/voteOnSponsorTime?userID=randomID2&UUID=incorrect-category&category=fakecategory", null, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 400) { + let row = db.prepare('get', "SELECT category FROM sponsorTimes WHERE UUID = ?", ["incorrect-category"]); + if (row.category === "sponsor") { + done(); + } else { + done("Vote did not succeed. Submission went from sponsor to " + row.category); + } + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should be able to change your vote for a category and it should immediately change (for now)', (done: Done) => { + request.get(getbaseURL() + + "/api/voteOnSponsorTime?userID=randomID2&UUID=vote-uuid-4&category=outro", null, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 200) { + let row = db.prepare('get', "SELECT category FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-4"]); + if (row.category === "outro") { + done(); + } else { + done("Vote did not succeed. Submission went from intro to " + row.category); + } + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + + it('Should not be able to change your vote to an invalid category', (done: Done) => { + const vote = (inputCat: string, assertCat: string, callback: Done) => { + request.get(getbaseURL() + + "/api/voteOnSponsorTime?userID=randomID2&UUID=incorrect-category-change&category=" + inputCat, null, + (err) => { + if (err) done(err); + else { + let row = db.prepare('get', "SELECT category FROM sponsorTimes WHERE UUID = ?", ["incorrect-category-change"]); + if (row.category === assertCat) { + callback(); + } else { + done("Vote did not succeed. Submission went from sponsor to " + row.category); + } + } + }); + }; + vote("sponsor", "sponsor", () => { + vote("fakeCategory", "sponsor", done); + }); + }); + + + it('VIP should be able to vote for a category and it should immediately change', (done: Done) => { + request.get(getbaseURL() + + "/api/voteOnSponsorTime?userID=VIPUser&UUID=vote-uuid-5&category=outro", null, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 200) { + let row = db.prepare('get', "SELECT category FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-5"]); + let row2 = db.prepare('get', "SELECT votes FROM categoryVotes WHERE UUID = ? and category = ?", ["vote-uuid-5", "outro"]); + if (row.category === "outro" && row2.votes === 500) { + done(); + } else { + done("Vote did not succeed. Submission went from intro to " + row.category + ". Category votes are " + row2.votes + " and should be 500."); + } + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should not be able to category-vote on an invalid UUID submission', (done: Done) => { + request.get(getbaseURL() + + "/api/voteOnSponsorTime?userID=randomID3&UUID=invalid-uuid&category=intro", null, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 400) { + done(); + } else { + done("Status code was " + res.statusCode + " instead of 400."); + } + }); + }); + + it('Non-VIP should not be able to upvote "dead" submission', (done: Done) => { + request.get(getbaseURL() + + "/api/voteOnSponsorTime?userID=randomID2&UUID=vote-uuid-5&type=1", null, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 403) { + done(); + } else { + done("Status code was " + res.statusCode + " instead of 403"); + } + }); + }); + + it('VIP should be able to upvote "dead" submission', (done: Done) => { + request.get(getbaseURL() + + "/api/voteOnSponsorTime?userID=VIPUser&UUID=vote-uuid-5&type=1", null, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 200) { + let row = db.prepare('get', "SELECT votes FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-5"]); + if (row.votes > -3) { + done(); + } else { + done("Vote did not succeed. Votes raised from -3 to " + row.votes); + } + } else { + done("Status code was " + res.statusCode); + } + }); + }); + + it('Should not be able to upvote a segment (Too many warning)', (done: Done) => { + request.get(getbaseURL() + + "/api/voteOnSponsorTime?userID=warn-voteuser01&UUID=warnvote-uuid-0&type=1", null, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 403) { + done(); // success + } else { + done("Status code was " + res.statusCode); + } + }); + }); + +}); diff --git a/test/mocks.js b/test/mocks.js deleted file mode 100644 index ddf7f19..0000000 --- a/test/mocks.js +++ /dev/null @@ -1,51 +0,0 @@ -var express = require('express'); -var app = express(); - -var config = require('../src/config.js'); - -app.post('/ReportChannelWebhook', (req, res) => { - res.sendStatus(200); -}); - -app.post('/FirstTimeSubmissionsWebhook', (req, res) => { - res.sendStatus(200); -}); - -app.post('/CompletelyIncorrectReportWebhook', (req, res) => { - res.sendStatus(200); -}); - -// Testing NeuralBlock -app.post('/NeuralBlockRejectWebhook', (req, res) => { - res.sendStatus(200); -}); - -app.get('/NeuralBlock/api/checkSponsorSegments', (req, res) => { - if (req.query.vid === "LevkAjUE6d4") { - res.json({ - probabilities: [0.69] - }); - return; - } - res.sendStatus(500); -}); - -//getSponsorSegments is no longer being used for automod -app.get('/NeuralBlock/api/getSponsorSegments', (req, res) => { - if (req.query.vid === "LevkAjUE6d4") { - res.json({ - sponsorSegments: [[0.47,7.549],[264.023,317.293]] - }); - return; - } - res.sendStatus(500); -}); - -// Testing webhooks -app.post('/CustomWebhook', (req, res) => { - res.sendStatus(200); -}); - -module.exports = function createMockServer(callback) { - return app.listen(config.mockPort, callback); -} diff --git a/test/mocks.ts b/test/mocks.ts new file mode 100644 index 0000000..dfa449b --- /dev/null +++ b/test/mocks.ts @@ -0,0 +1,51 @@ +import express from 'express'; +import {config} from '../src/config'; + +const app = express(); + +app.post('/ReportChannelWebhook', (req, res) => { + res.sendStatus(200); +}); + +app.post('/FirstTimeSubmissionsWebhook', (req, res) => { + res.sendStatus(200); +}); + +app.post('/CompletelyIncorrectReportWebhook', (req, res) => { + res.sendStatus(200); +}); + +// Testing NeuralBlock +app.post('/NeuralBlockRejectWebhook', (req, res) => { + res.sendStatus(200); +}); + +app.get('/NeuralBlock/api/checkSponsorSegments', (req, res) => { + if (req.query.vid === "LevkAjUE6d4") { + res.json({ + probabilities: [0.69], + }); + return; + } + res.sendStatus(500); +}); + +//getSponsorSegments is no longer being used for automod +app.get('/NeuralBlock/api/getSponsorSegments', (req, res) => { + if (req.query.vid === "LevkAjUE6d4") { + res.json({ + sponsorSegments: [[0.47, 7.549], [264.023, 317.293]], + }); + return; + } + res.sendStatus(500); +}); + +// Testing webhooks +app.post('/CustomWebhook', (req, res) => { + res.sendStatus(200); +}); + +export function createMockServer(callback: () => void) { + return app.listen(config.mockPort, callback); +} diff --git a/test/utils.js b/test/utils.js deleted file mode 100644 index 1067f3f..0000000 --- a/test/utils.js +++ /dev/null @@ -1,7 +0,0 @@ -var config = require('../src/config.js'); - -module.exports = { - getbaseURL: () => { - return "http://localhost:" + config.port; - } -}; \ No newline at end of file diff --git a/test/utils.ts b/test/utils.ts new file mode 100644 index 0000000..c76065c --- /dev/null +++ b/test/utils.ts @@ -0,0 +1,10 @@ +import {config} from '../src/config'; + +export function getbaseURL() { + return "http://localhost:" + config.port; +} + +/** + * Duplicated from Mocha types. TypeScript doesn't infer that type by itself for some reason. + */ +export type Done = (err?: any) => void; diff --git a/test/youtubeMock.js b/test/youtubeMock.js deleted file mode 100644 index 7e15f64..0000000 --- a/test/youtubeMock.js +++ /dev/null @@ -1,72 +0,0 @@ -/* -YouTubeAPI.videos.list({ - part: "snippet", - id: videoID -}, function (err, data) {}); - */ - - // https://developers.google.com/youtube/v3/docs/videos - -const YouTubeAPI = { - listVideos: (id, callback) => { - YouTubeAPI.videos.list({ - id: id - }, callback); - }, - videos: { - list: (obj, callback) => { - if (obj.id === "knownWrongID") { - callback(undefined, { - pageInfo: { - totalResults: 0 - }, - items: [] - }); - } if (obj.id === "noDuration") { - callback(undefined, { - pageInfo: { - totalResults: 1 - }, - items: [ - { - contentDetails: { - duration: "PT0S" - }, - snippet: { - title: "Example Title", - thumbnails: { - maxres: { - url: "https://sponsor.ajay.app/LogoSponsorBlockSimple256px.png" - } - } - } - } - ] - }); - } else { - callback(undefined, { - pageInfo: { - totalResults: 1 - }, - items: [ - { - contentDetails: { - duration: "PT1H23M30S" - }, - snippet: { - title: "Example Title", - thumbnails: { - maxres: { - url: "https://sponsor.ajay.app/LogoSponsorBlockSimple256px.png" - } - } - } - } - ] - }); - } - } - } -}; - -module.exports = YouTubeAPI; \ No newline at end of file diff --git a/test/youtubeMock.ts b/test/youtubeMock.ts new file mode 100644 index 0000000..5e32acc --- /dev/null +++ b/test/youtubeMock.ts @@ -0,0 +1,71 @@ +/* +YouTubeAPI.videos.list({ + part: "snippet", + id: videoID +}, function (err, data) {}); + */ + +// https://developers.google.com/youtube/v3/docs/videos + +export const YouTubeAPI = { + listVideos: (id: string, callback: (ytErr: any, data: any) => void) => { + YouTubeAPI.videos.list({ + id: id, + }, callback); + }, + videos: { + list: (obj: { part: string; id: any } | { id: string }, callback: (ytErr: any, data: any) => void) => { + if (obj.id === "knownWrongID") { + callback(undefined, { + pageInfo: { + totalResults: 0, + }, + items: [], + }); + } + if (obj.id === "noDuration") { + callback(undefined, { + pageInfo: { + totalResults: 1, + }, + items: [ + { + contentDetails: { + duration: "PT0S", + }, + snippet: { + title: "Example Title", + thumbnails: { + maxres: { + url: "https://sponsor.ajay.app/LogoSponsorBlockSimple256px.png", + }, + }, + }, + }, + ], + }); + } else { + callback(undefined, { + pageInfo: { + totalResults: 1, + }, + items: [ + { + contentDetails: { + duration: "PT1H23M30S", + }, + snippet: { + title: "Example Title", + thumbnails: { + maxres: { + url: "https://sponsor.ajay.app/LogoSponsorBlockSimple256px.png", + }, + }, + }, + }, + ], + }); + } + }, + }, +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..d00f210 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,80 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "es2016", + /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ + "module": "commonjs", + /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + // "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + // "outDir": "./", /* Redirect output structure to the directory. */ + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true, + /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + "strictNullChecks": false, + /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + + /* Module Resolution Options */ + "moduleResolution": "node", + /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + "typeRoots": [ + "node_modules/@types" + ], + /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, + /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "skipLibCheck": true, + /* Skip type checking of declaration files. */ + "forceConsistentCasingInFileNames": true + /* Disallow inconsistently-cased references to the same file. */ + } +}