mirror of
https://github.com/ajayyy/SponsorBlockServer.git
synced 2026-01-01 22:29:15 +03:00
Improve performance of segment querying endpoints
This commit is contained in:
@@ -8,51 +8,102 @@ var logger = require('../utils/logger.js');
|
|||||||
var getHash = require('../utils/getHash.js');
|
var getHash = require('../utils/getHash.js');
|
||||||
var getIP = require('../utils/getIP.js');
|
var getIP = require('../utils/getIP.js');
|
||||||
|
|
||||||
function cleanGetSegments(req, videoID, categories) {
|
function prepareCategorySegments(req, videoID, category, segments, cache = {shadowHiddenSegments: {}}) {
|
||||||
let userHashedIP, shadowHiddenSegments;
|
const filteredSegments = segments.filter((segment) => {
|
||||||
|
if (segment.votes < -1) {
|
||||||
|
return false; //too untrustworthy, just ignore it
|
||||||
|
}
|
||||||
|
|
||||||
let segments = [];
|
//check if shadowHidden
|
||||||
|
//this means it is hidden to everyone but the original ip that submitted it
|
||||||
|
if (segment.shadowHidden != 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cache.shadowHiddenSegments[videoID] === undefined) {
|
||||||
|
cache.shadowHiddenSegments[videoID] = privateDB.prepare('all', 'SELECT hashedIP FROM sponsorTimes WHERE videoID = ?', [videoID]);
|
||||||
|
}
|
||||||
|
|
||||||
|
//if this isn't their ip, don't send it to them
|
||||||
|
return cache.shadowHiddenSegments[videoID].some((shadowHiddenSegment) => {
|
||||||
|
if (cache.userHashedIP === undefined) {
|
||||||
|
//hash the IP only if it's strictly necessary
|
||||||
|
cache.userHashedIP = getHash(getIP(req) + config.globalSalt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return shadowHiddenSegment.hashedIP === cache.userHashedIP;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return chooseSegments(filteredSegments).map((chosenSegment) => ({
|
||||||
|
category,
|
||||||
|
segment: [chosenSegment.startTime, chosenSegment.endTime],
|
||||||
|
UUID: chosenSegment.UUID,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSegmentsByVideoID(req, videoID, categories) {
|
||||||
|
const cache = {};
|
||||||
|
const segments = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (const category of categories) {
|
const segmentsByCategory = db
|
||||||
const categorySegments = db
|
.prepare(
|
||||||
.prepare(
|
'all',
|
||||||
'all',
|
`SELECT startTime, endTime, votes, UUID, category, shadowHidden FROM sponsorTimes WHERE videoID = ? AND category IN (${Array(categories.length).fill('?').join()}) ORDER BY startTime`,
|
||||||
'SELECT startTime, endTime, votes, UUID, shadowHidden FROM sponsorTimes WHERE videoID = ? and category = ? ORDER BY startTime',
|
[videoID, categories]
|
||||||
[videoID, category]
|
).reduce((acc, segment) => {
|
||||||
)
|
acc[segment.category] = acc[segment.category] || [];
|
||||||
.filter(segment => {
|
acc[segment.category].push(segment);
|
||||||
if (segment.votes < -1) {
|
|
||||||
return false; //too untrustworthy, just ignore it
|
|
||||||
}
|
|
||||||
|
|
||||||
//check if shadowHidden
|
return acc;
|
||||||
//this means it is hidden to everyone but the original ip that submitted it
|
}, {});
|
||||||
if (segment.shadowHidden != 1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shadowHiddenSegments === undefined) {
|
for (const [category, categorySegments] of Object.entries(segmentsByCategory)) {
|
||||||
shadowHiddenSegments = privateDB.prepare('all', 'SELECT hashedIP FROM sponsorTimes WHERE videoID = ?', [videoID]);
|
segments.push(...prepareCategorySegments(req, videoID, category, categorySegments, cache));
|
||||||
}
|
}
|
||||||
|
|
||||||
//if this isn't their ip, don't send it to them
|
return segments;
|
||||||
return shadowHiddenSegments.some(shadowHiddenSegment => {
|
} catch (err) {
|
||||||
if (userHashedIP === undefined) {
|
if (err) {
|
||||||
//hash the IP only if it's strictly necessary
|
logger.error(err);
|
||||||
userHashedIP = getHash(getIP(req) + config.globalSalt);
|
return null;
|
||||||
}
|
}
|
||||||
return shadowHiddenSegment.hashedIP === userHashedIP;
|
}
|
||||||
});
|
}
|
||||||
});
|
|
||||||
|
|
||||||
chooseSegments(categorySegments).forEach(chosenSegment => {
|
function getSegmentsByHash(req, hashedVideoIDPrefix, categories) {
|
||||||
segments.push({
|
const cache = {};
|
||||||
category,
|
const segments = {};
|
||||||
segment: [chosenSegment.startTime, chosenSegment.endTime],
|
|
||||||
UUID: chosenSegment.UUID,
|
try {
|
||||||
});
|
const allSegments = db
|
||||||
});
|
.prepare(
|
||||||
|
'all',
|
||||||
|
`SELECT videoID, startTime, endTime, votes, UUID, category, shadowHidden, hashedVideoID FROM sponsorTimes WHERE hashedVideoID LIKE ? AND category IN (${Array(categories.length).fill('?').join()}) ORDER BY startTime`,
|
||||||
|
[hashedVideoIDPrefix + '%', categories]
|
||||||
|
).reduce((acc, segment) => {
|
||||||
|
acc[segment.videoID] = acc[segment.videoID] || {
|
||||||
|
hash: segment.hashedVideoID,
|
||||||
|
categories: {},
|
||||||
|
};
|
||||||
|
const videoCategories = acc[segment.videoID].categories;
|
||||||
|
|
||||||
|
videoCategories[segment.category] = videoCategories[segment.category] || [];
|
||||||
|
videoCategories[segment.category].push(segment);
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
for (const [videoID, videoData] of Object.entries(allSegments)) {
|
||||||
|
segments[videoID] = {
|
||||||
|
hash: videoData.hash,
|
||||||
|
segments: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [category, categorySegments] of Object.entries(videoData.categories)) {
|
||||||
|
segments[videoID].segments.push(...prepareCategorySegments(req, videoID, category, categorySegments, cache));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return segments;
|
return segments;
|
||||||
@@ -160,14 +211,14 @@ function handleGetSegments(req, res) {
|
|||||||
? [req.query.category]
|
? [req.query.category]
|
||||||
: ['sponsor'];
|
: ['sponsor'];
|
||||||
|
|
||||||
let segments = cleanGetSegments(req, videoID, categories);
|
const segments = getSegmentsByVideoID(req, videoID, categories);
|
||||||
|
|
||||||
if (segments === null || segments === undefined) {
|
if (segments === null || segments === undefined) {
|
||||||
res.sendStatus(500);
|
res.sendStatus(500);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (segments.length == 0) {
|
if (segments.length === 0) {
|
||||||
res.sendStatus(404);
|
res.sendStatus(404);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -177,7 +228,8 @@ function handleGetSegments(req, res) {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
handleGetSegments,
|
handleGetSegments,
|
||||||
cleanGetSegments,
|
getSegmentsByVideoID,
|
||||||
|
getSegmentsByHash,
|
||||||
endpoint: function (req, res) {
|
endpoint: function (req, res) {
|
||||||
let segments = handleGetSegments(req, res);
|
let segments = handleGetSegments(req, res);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const hashPrefixTester = require('../utils/hashPrefixTester.js');
|
const hashPrefixTester = require('../utils/hashPrefixTester.js');
|
||||||
const getSegments = require('./getSkipSegments.js').cleanGetSegments;
|
const getSegments = require('./getSkipSegments.js').getSegmentsByHash;
|
||||||
|
|
||||||
const databases = require('../databases/databases.js');
|
const databases = require('../databases/databases.js');
|
||||||
const logger = require('../utils/logger.js');
|
const logger = require('../utils/logger.js');
|
||||||
@@ -19,15 +19,15 @@ module.exports = async function (req, res) {
|
|||||||
: ['sponsor'];
|
: ['sponsor'];
|
||||||
|
|
||||||
// Get all video id's that match hash prefix
|
// 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 segments = getSegments(req, hashPrefix, categories);
|
||||||
|
|
||||||
let segments = videoIds.map((video) => {
|
if (!segments) return res.status(404).json([]);
|
||||||
return {
|
|
||||||
videoID: video.videoID,
|
|
||||||
hash: video.hashedVideoID,
|
|
||||||
segments: getSegments(req, video.videoID, categories)
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
res.status((segments.length === 0) ? 404 : 200).json(segments);
|
const output = Object.entries(segments).map(([videoID, data]) => ({
|
||||||
}
|
videoID,
|
||||||
|
hash: data.hash,
|
||||||
|
segments: data.segments,
|
||||||
|
}));
|
||||||
|
|
||||||
|
res.status(output.length === 0 ? 404 : 200).json(output);
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,17 +24,17 @@ describe('getSegmentsByHash', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should be able to get a 200 with empty segments for video but no matching categories', (done) => {
|
it('Should return 404 if no segments are found even if a video for the given hash is known', (done) => {
|
||||||
request.get(utils.getbaseURL()
|
request.get(utils.getbaseURL()
|
||||||
+ '/api/skipSegments/3272f?categories=["shilling"]', null,
|
+ '/api/skipSegments/3272f?categories=["shilling"]', null,
|
||||||
(err, res, body) => {
|
(err, res, body) => {
|
||||||
if (err) done("Couldn't call endpoint");
|
if (err) done("Couldn't call endpoint");
|
||||||
else if (res.statusCode !== 200) done("non 200 status code, was " + res.statusCode);
|
else if (res.statusCode !== 404) done("non 404 status code, was " + res.statusCode);
|
||||||
else {
|
else {
|
||||||
if (JSON.parse(body) && JSON.parse(body).length > 0 && JSON.parse(body)[0].segments.length === 0) {
|
if (body === '[]') {
|
||||||
done(); // pass
|
done(); // pass
|
||||||
} else {
|
} else {
|
||||||
done("response had segments");
|
done("response had videos");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user