mirror of
https://github.com/ajayyy/SponsorBlockServer.git
synced 2024-11-10 01:02:30 +01:00
fix docker build, add proper mocks to tests, remove YouTubeAPI dependency on youtube test mock, move index.ts and test.ts to /src ant /test folders
This commit is contained in:
parent
62b008e693
commit
12729b36fb
17 changed files with 365 additions and 207 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -104,3 +104,4 @@ config.json
|
|||
# Mac files
|
||||
.DS_Store
|
||||
/.idea/
|
||||
/dist/
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
FROM node:12
|
||||
WORKDIR /usr/src/app
|
||||
COPY package.json .
|
||||
RUN npm install
|
||||
COPY index.ts .
|
||||
COPY package-lock.json .
|
||||
COPY tsconfig.json .
|
||||
COPY src src
|
||||
RUN npm ci
|
||||
RUN npm run tsc
|
||||
RUN mkdir databases
|
||||
COPY databases/*.sql databases/
|
||||
COPY entrypoint.sh .
|
||||
EXPOSE 8080
|
||||
CMD ./entrypoint.sh
|
||||
CMD ./entrypoint.sh
|
||||
|
|
|
@ -23,4 +23,4 @@ cp /etc/sponsorblock/config.json . || cat <<EOF > config.json
|
|||
"readOnly": false
|
||||
}
|
||||
EOF
|
||||
ts-node index.ts
|
||||
node dist/index.js
|
||||
|
|
9
index.ts
9
index.ts
|
@ -1,9 +0,0 @@
|
|||
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 + ".");
|
||||
});
|
139
package-lock.json
generated
139
package-lock.json
generated
|
@ -4,6 +4,51 @@
|
|||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@sinonjs/commons": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz",
|
||||
"integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"type-detect": "4.0.8"
|
||||
}
|
||||
},
|
||||
"@sinonjs/fake-timers": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz",
|
||||
"integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@sinonjs/commons": "^1.7.0"
|
||||
}
|
||||
},
|
||||
"@sinonjs/formatio": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-5.0.1.tgz",
|
||||
"integrity": "sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@sinonjs/commons": "^1",
|
||||
"@sinonjs/samsam": "^5.0.2"
|
||||
}
|
||||
},
|
||||
"@sinonjs/samsam": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.2.0.tgz",
|
||||
"integrity": "sha512-CaIcyX5cDsjcW/ab7HposFWzV1kC++4HNsfnEdFJa7cP1QIuILAKV+BgfeqRXhcnSAc76r/Rh/O5C+300BwUIw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@sinonjs/commons": "^1.6.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"type-detect": "^4.0.8"
|
||||
}
|
||||
},
|
||||
"@sinonjs/text-encoding": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz",
|
||||
"integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/better-sqlite3": {
|
||||
"version": "5.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-5.4.0.tgz",
|
||||
|
@ -1510,6 +1555,12 @@
|
|||
"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=",
|
||||
"dev": true
|
||||
},
|
||||
"isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
|
@ -1575,6 +1626,12 @@
|
|||
"verror": "1.10.0"
|
||||
}
|
||||
},
|
||||
"just-extend": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz",
|
||||
"integrity": "sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==",
|
||||
"dev": true
|
||||
},
|
||||
"jwa": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz",
|
||||
|
@ -1619,6 +1676,12 @@
|
|||
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.get": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
|
||||
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=",
|
||||
"dev": true
|
||||
},
|
||||
"log-symbols": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz",
|
||||
|
@ -1897,6 +1960,30 @@
|
|||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
||||
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
|
||||
},
|
||||
"nise": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/nise/-/nise-4.0.4.tgz",
|
||||
"integrity": "sha512-bTTRUNlemx6deJa+ZyoCUTRvH3liK5+N6VQZ4NIw90AgDXY6iPnsqplNFf6STcj+ePk0H/xqxnP75Lr0J0Fq3A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@sinonjs/commons": "^1.7.0",
|
||||
"@sinonjs/fake-timers": "^6.0.0",
|
||||
"@sinonjs/text-encoding": "^0.7.1",
|
||||
"just-extend": "^4.0.2",
|
||||
"path-to-regexp": "^1.7.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"path-to-regexp": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
|
||||
"integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"isarray": "0.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"node-environment-flags": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz",
|
||||
|
@ -2432,6 +2519,29 @@
|
|||
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
|
||||
"dev": true
|
||||
},
|
||||
"sinon": {
|
||||
"version": "9.2.0",
|
||||
"resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.0.tgz",
|
||||
"integrity": "sha512-eSNXz1XMcGEMHw08NJXSyTHIu6qTCOiN8x9ODACmZpNQpr0aXTBXBnI4xTzQzR+TEpOmLiKowGf9flCuKIzsbw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@sinonjs/commons": "^1.8.1",
|
||||
"@sinonjs/fake-timers": "^6.0.1",
|
||||
"@sinonjs/formatio": "^5.0.1",
|
||||
"@sinonjs/samsam": "^5.2.0",
|
||||
"diff": "^4.0.2",
|
||||
"nise": "^4.0.4",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"diff": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
|
@ -2561,6 +2671,23 @@
|
|||
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
|
||||
"dev": true
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"sync-mysql": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/sync-mysql/-/sync-mysql-3.0.1.tgz",
|
||||
|
@ -2701,6 +2828,12 @@
|
|||
"punycode": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"ts-mock-imports": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-mock-imports/-/ts-mock-imports-1.3.0.tgz",
|
||||
"integrity": "sha512-cCrVcRYsp84eDvPict0ZZD/D7ppQ0/JSx4ve6aEU8DjlsaWRJWV6ADMovp2sCuh6pZcduLFoIYhKTDU2LARo7Q==",
|
||||
"dev": true
|
||||
},
|
||||
"ts-node": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz",
|
||||
|
@ -2735,6 +2868,12 @@
|
|||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
|
||||
},
|
||||
"type-detect": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
|
||||
"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
|
||||
"dev": true
|
||||
},
|
||||
"type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
|
|
|
@ -2,12 +2,13 @@
|
|||
"name": "sponsor_block_server",
|
||||
"version": "0.1.0",
|
||||
"description": "Server that holds the SponsorBlock database",
|
||||
"main": "index.ts",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"test": "ts-node test.ts",
|
||||
"test": "ts-node test/test.ts",
|
||||
"dev": "nodemon -x \"(npm test || echo test failed) && npm start\"",
|
||||
"dev:bash": "nodemon -x 'npm test ; npm start'",
|
||||
"start": "ts-node index.ts"
|
||||
"start": "ts-node src/index.ts",
|
||||
"tsc": "tsc -p tsconfig.json"
|
||||
},
|
||||
"author": "Ajay Ramachandran",
|
||||
"license": "MIT",
|
||||
|
@ -35,6 +36,8 @@
|
|||
"@types/request": "^2.48.5",
|
||||
"mocha": "^7.1.1",
|
||||
"nodemon": "^2.0.2",
|
||||
"sinon": "^9.2.0",
|
||||
"ts-mock-imports": "^1.3.0",
|
||||
"ts-node": "^9.0.0",
|
||||
"typescript": "^4.0.3"
|
||||
}
|
||||
|
|
198
src/app.ts
198
src/app.ts
|
@ -1,4 +1,4 @@
|
|||
import express, {Request, RequestHandler, Response} from 'express';
|
||||
import express, {Express, Request, RequestHandler, Response} from 'express';
|
||||
import {config} from './config';
|
||||
import {oldSubmitSponsorTimes} from './routes/oldSubmitSponsorTimes';
|
||||
import {oldGetVideoSponsorTimes} from './routes/oldGetVideoSponsorTimes';
|
||||
|
@ -22,108 +22,112 @@ 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).
|
||||
const app = express();
|
||||
// Rate limit endpoint lists
|
||||
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));
|
||||
}
|
||||
|
||||
//setup CORS correctly
|
||||
app.use(corsMiddleware);
|
||||
app.use(loggerMiddleware);
|
||||
app.use(express.json());
|
||||
|
||||
if (config.userCounterURL) app.use(userCounter);
|
||||
|
||||
// Setup pretty JSON
|
||||
if (config.mode === "development") app.set('json spaces', 2);
|
||||
|
||||
// Set production mode
|
||||
app.set('env', config.mode || 'production');
|
||||
|
||||
//add the get function
|
||||
app.get('/api/getVideoSponsorTimes', oldGetVideoSponsorTimes);
|
||||
|
||||
//add the oldpost function
|
||||
app.get('/api/postVideoSponsorTimes', oldSubmitSponsorTimes);
|
||||
app.post('/api/postVideoSponsorTimes', oldSubmitSponsorTimes);
|
||||
|
||||
//add the skip segments functions
|
||||
app.get('/api/skipSegments', getSkipSegments);
|
||||
app.post('/api/skipSegments', postSkipSegments);
|
||||
|
||||
// add the privacy protecting skip segments functions
|
||||
app.get('/api/skipSegments/:prefix', getSkipSegmentsByHash);
|
||||
|
||||
//voting endpoint
|
||||
app.get('/api/voteOnSponsorTime', ...voteEndpoints);
|
||||
app.post('/api/voteOnSponsorTime', ...voteEndpoints);
|
||||
|
||||
//Endpoint when a submission is skipped
|
||||
app.get('/api/viewedVideoSponsorTime', ...viewEndpoints);
|
||||
app.post('/api/viewedVideoSponsorTime', ...viewEndpoints);
|
||||
|
||||
//To set your username for the stats view
|
||||
app.post('/api/setUsername', setUsername);
|
||||
|
||||
//get what username this user has
|
||||
app.get('/api/getUsername', getUsername);
|
||||
|
||||
//Endpoint used to hide a certain user's data
|
||||
app.post('/api/shadowBanUser', shadowBanUser);
|
||||
|
||||
//Endpoint used to make a user a VIP user with special privileges
|
||||
app.post('/api/addUserAsVIP', addUserAsVIP);
|
||||
|
||||
//Gets all the views added up for one userID
|
||||
//Useful to see how much one user has contributed
|
||||
app.get('/api/getViewsForUser', getViewsForUser);
|
||||
|
||||
//Gets all the saved time added up (views * sponsor length) for one userID
|
||||
//Useful to see how much one user has contributed
|
||||
//In minutes
|
||||
app.get('/api/getSavedTimeForUser', getSavedTimeForUser);
|
||||
|
||||
app.get('/api/getTopUsers', getTopUsers);
|
||||
|
||||
//send out totals
|
||||
//send the total submissions, total views and total minutes saved
|
||||
app.get('/api/getTotalStats', getTotalStats);
|
||||
|
||||
app.get('/api/getUserInfo', getUserInfo);
|
||||
|
||||
//send out a formatted time saved total
|
||||
app.get('/api/getDaysSavedFormatted', getDaysSavedFormatted);
|
||||
|
||||
//submit video containing no segments
|
||||
app.post('/api/noSegments', postNoSegments);
|
||||
|
||||
app.delete('/api/noSegments', deleteNoSegments);
|
||||
|
||||
//get if user is a vip
|
||||
app.get('/api/isUserVIP', getIsUserVIP);
|
||||
|
||||
//sent user a warning
|
||||
app.post('/api/warnUser', postWarning);
|
||||
|
||||
//get if user is a vip
|
||||
app.post('/api/segmentShift', postSegmentShift);
|
||||
|
||||
app.get('/database.db', function (req: Request, res: Response) {
|
||||
res.sendFile("./databases/sponsorTimes.db", {root: "./"});
|
||||
});
|
||||
|
||||
// Create an HTTP service.
|
||||
export function createServer(callback: () => void) {
|
||||
// Create a service (the app object is just a callback).
|
||||
const app = express();
|
||||
|
||||
//setup CORS correctly
|
||||
app.use(corsMiddleware);
|
||||
app.use(loggerMiddleware);
|
||||
app.use(express.json());
|
||||
|
||||
if (config.userCounterURL) app.use(userCounter);
|
||||
|
||||
// Setup pretty JSON
|
||||
if (config.mode === "development") app.set('json spaces', 2);
|
||||
|
||||
// Set production mode
|
||||
app.set('env', config.mode || 'production');
|
||||
|
||||
setupRoutes(app);
|
||||
|
||||
return app.listen(config.port, callback);
|
||||
}
|
||||
|
||||
function setupRoutes(app: Express) {
|
||||
// Rate limit endpoint lists
|
||||
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));
|
||||
}
|
||||
|
||||
//add the get function
|
||||
app.get('/api/getVideoSponsorTimes', oldGetVideoSponsorTimes);
|
||||
|
||||
//add the oldpost function
|
||||
app.get('/api/postVideoSponsorTimes', oldSubmitSponsorTimes);
|
||||
app.post('/api/postVideoSponsorTimes', oldSubmitSponsorTimes);
|
||||
|
||||
//add the skip segments functions
|
||||
app.get('/api/skipSegments', getSkipSegments);
|
||||
app.post('/api/skipSegments', postSkipSegments);
|
||||
|
||||
// add the privacy protecting skip segments functions
|
||||
app.get('/api/skipSegments/:prefix', getSkipSegmentsByHash);
|
||||
|
||||
//voting endpoint
|
||||
app.get('/api/voteOnSponsorTime', ...voteEndpoints);
|
||||
app.post('/api/voteOnSponsorTime', ...voteEndpoints);
|
||||
|
||||
//Endpoint when a submission is skipped
|
||||
app.get('/api/viewedVideoSponsorTime', ...viewEndpoints);
|
||||
app.post('/api/viewedVideoSponsorTime', ...viewEndpoints);
|
||||
|
||||
//To set your username for the stats view
|
||||
app.post('/api/setUsername', setUsername);
|
||||
|
||||
//get what username this user has
|
||||
app.get('/api/getUsername', getUsername);
|
||||
|
||||
//Endpoint used to hide a certain user's data
|
||||
app.post('/api/shadowBanUser', shadowBanUser);
|
||||
|
||||
//Endpoint used to make a user a VIP user with special privileges
|
||||
app.post('/api/addUserAsVIP', addUserAsVIP);
|
||||
|
||||
//Gets all the views added up for one userID
|
||||
//Useful to see how much one user has contributed
|
||||
app.get('/api/getViewsForUser', getViewsForUser);
|
||||
|
||||
//Gets all the saved time added up (views * sponsor length) for one userID
|
||||
//Useful to see how much one user has contributed
|
||||
//In minutes
|
||||
app.get('/api/getSavedTimeForUser', getSavedTimeForUser);
|
||||
|
||||
app.get('/api/getTopUsers', getTopUsers);
|
||||
|
||||
//send out totals
|
||||
//send the total submissions, total views and total minutes saved
|
||||
app.get('/api/getTotalStats', getTotalStats);
|
||||
|
||||
app.get('/api/getUserInfo', getUserInfo);
|
||||
|
||||
//send out a formatted time saved total
|
||||
app.get('/api/getDaysSavedFormatted', getDaysSavedFormatted);
|
||||
|
||||
//submit video containing no segments
|
||||
app.post('/api/noSegments', postNoSegments);
|
||||
|
||||
app.delete('/api/noSegments', deleteNoSegments);
|
||||
|
||||
//get if user is a vip
|
||||
app.get('/api/isUserVIP', getIsUserVIP);
|
||||
|
||||
//sent user a warning
|
||||
app.post('/api/warnUser', postWarning);
|
||||
|
||||
//get if user is a vip
|
||||
app.post('/api/segmentShift', postSegmentShift);
|
||||
|
||||
app.get('/database.db', function (req: Request, res: Response) {
|
||||
res.sendFile("./databases/sponsorTimes.db", {root: "./"});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import fs from 'fs';
|
||||
import {SBSConfig} from "./types/config.model";
|
||||
|
||||
const isTestMode = process.env.npm_lifecycle_script === 'ts-node test.ts';
|
||||
const isTestMode = process.env.npm_lifecycle_script === 'ts-node test/test.ts';
|
||||
const configFile = isTestMode ? 'test.json' : 'config.json';
|
||||
export const config: SBSConfig = JSON.parse(fs.readFileSync(configFile).toString('utf8'));
|
||||
|
||||
|
|
9
src/index.ts
Normal file
9
src/index.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import {config} from "./config";
|
||||
import {initDb} from './databases/databases';
|
||||
import {createServer} from "./app";
|
||||
import {Logger} from "./utils/logger";
|
||||
|
||||
initDb();
|
||||
createServer(() => {
|
||||
Logger.info("Server started on port " + config.port + ".");
|
||||
});
|
|
@ -13,9 +13,5 @@ export function rateLimitMiddleware(limitConfig: RateLimitConfig): rateLimit.Rat
|
|||
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';
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,28 +2,15 @@ import {config} from '../config';
|
|||
import {Logger} from './logger';
|
||||
import * as redis from './redis';
|
||||
// @ts-ignore
|
||||
import YouTubeAPI from 'youtube-api';
|
||||
import _youTubeAPI from 'youtube-api';
|
||||
|
||||
import {YouTubeAPI as youtubeApiTest} from '../../test/youtubeMock';
|
||||
_youTubeAPI.authenticate({
|
||||
type: "key",
|
||||
key: config.youtubeAPIKey,
|
||||
});
|
||||
|
||||
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) => {
|
||||
export class YouTubeAPI {
|
||||
static 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);
|
||||
|
@ -34,7 +21,7 @@ else {
|
|||
redis.get(redisKey, (getErr: string, result: string) => {
|
||||
if (getErr || !result) {
|
||||
Logger.debug("redis: no cache for video information: " + videoID);
|
||||
YouTubeAPI.videos.list({
|
||||
_youTubeAPI.videos.list({
|
||||
part,
|
||||
id: videoID,
|
||||
}, (ytErr: boolean | string, data: any) => {
|
||||
|
@ -63,7 +50,3 @@ else {
|
|||
});
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
_youtubeApi as YouTubeAPI
|
||||
}
|
||||
|
|
|
@ -2,8 +2,13 @@ import request from 'request';
|
|||
import {db} from '../../src/databases/databases';
|
||||
import {Done, getbaseURL} from '../utils';
|
||||
import {getHash} from '../../src/utils/getHash';
|
||||
import {ImportMock,} from 'ts-mock-imports';
|
||||
import * as YouTubeAPIModule from '../../src/utils/youtubeApi';
|
||||
import {YouTubeApiMock} from '../youtubeMock';
|
||||
|
||||
|
||||
const mockManager = ImportMock.mockStaticClass(YouTubeAPIModule, 'YouTubeAPI');
|
||||
const sinonStub = mockManager.mock('listVideos');
|
||||
sinonStub.callsFake(YouTubeApiMock.listVideos);
|
||||
|
||||
describe('getSegmentsByHash', () => {
|
||||
before(() => {
|
||||
|
@ -12,6 +17,7 @@ describe('getSegmentsByHash', () => {
|
|||
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) => {
|
||||
|
|
|
@ -3,6 +3,13 @@ import {config} from '../../src/config';
|
|||
import {getHash} from '../../src/utils/getHash';
|
||||
import {Done, getbaseURL} from '../utils';
|
||||
import {db} from '../../src/databases/databases';
|
||||
import {ImportMock} from 'ts-mock-imports';
|
||||
import * as YouTubeAPIModule from '../../src/utils/youtubeApi';
|
||||
import {YouTubeApiMock} from '../youtubeMock';
|
||||
|
||||
const mockManager = ImportMock.mockStaticClass(YouTubeAPIModule, 'YouTubeAPI');
|
||||
const sinonStub = mockManager.mock('listVideos');
|
||||
sinonStub.callsFake(YouTubeApiMock.listVideos);
|
||||
|
||||
describe('postSkipSegments', () => {
|
||||
before(() => {
|
||||
|
@ -227,7 +234,7 @@ describe('postSkipSegments', () => {
|
|||
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;
|
||||
let success = 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") ||
|
||||
|
|
|
@ -3,6 +3,13 @@ import {config} from '../../src/config';
|
|||
import {db, privateDB} from '../../src/databases/databases';
|
||||
import {Done, getbaseURL} from '../utils';
|
||||
import {getHash} from '../../src/utils/getHash';
|
||||
import {ImportMock} from 'ts-mock-imports';
|
||||
import * as YouTubeAPIModule from '../../src/utils/youtubeApi';
|
||||
import {YouTubeApiMock} from '../youtubeMock';
|
||||
|
||||
const mockManager = ImportMock.mockStaticClass(YouTubeAPIModule, 'YouTubeAPI');
|
||||
const sinonStub = mockManager.mock('listVideos');
|
||||
sinonStub.callsFake(YouTubeApiMock.listVideos);
|
||||
|
||||
describe('voteOnSponsorTime', () => {
|
||||
before(() => {
|
||||
|
|
|
@ -1,11 +1,20 @@
|
|||
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';
|
||||
import {config} from '../src/config';
|
||||
import {createServer} from '../src/app';
|
||||
import {createMockServer} from './mocks';
|
||||
import {Logger} from '../src/utils/logger';
|
||||
import {initDb} from '../src/databases/databases';
|
||||
import {ImportMock} from 'ts-mock-imports';
|
||||
import * as rateLimitMiddlewareModule from '../src/middleware/requestRateLimit';
|
||||
import rateLimit from 'express-rate-limit';
|
||||
|
||||
ImportMock.mockFunction(rateLimitMiddlewareModule, 'rateLimitMiddleware', rateLimit({
|
||||
skip: () => {
|
||||
return true;
|
||||
}
|
||||
}));
|
||||
|
||||
// delete old test database
|
||||
if (fs.existsSync(config.db)) fs.unlinkSync(config.db)
|
|
@ -7,65 +7,63 @@ YouTubeAPI.videos.list({
|
|||
|
||||
// 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",
|
||||
},
|
||||
|
||||
export class YouTubeApiMock {
|
||||
static listVideos(videoID: string, callback: (ytErr: any, data: any) => void) {
|
||||
const obj = {
|
||||
id: videoID
|
||||
};
|
||||
|
||||
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",
|
||||
},
|
||||
],
|
||||
});
|
||||
} else {
|
||||
callback(undefined, {
|
||||
pageInfo: {
|
||||
totalResults: 1,
|
||||
},
|
||||
items: [
|
||||
{
|
||||
contentDetails: {
|
||||
duration: "PT1H23M30S",
|
||||
},
|
||||
snippet: {
|
||||
title: "Example Title",
|
||||
thumbnails: {
|
||||
maxres: {
|
||||
url: "https://sponsor.ajay.app/LogoSponsorBlockSimple256px.png",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
// "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. */
|
||||
"outDir": "./dist", /* 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 */
|
||||
|
@ -76,5 +76,8 @@
|
|||
/* Skip type checking of declaration files. */
|
||||
"forceConsistentCasingInFileNames": true
|
||||
/* Disallow inconsistently-cased references to the same file. */
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue