mirror of
https://github.com/ajayyy/SponsorBlockServer.git
synced 2025-12-16 08:26:59 +03:00
Limit overlapping segments to just one through weighted randomness
This commit is contained in:
@@ -12,24 +12,64 @@ const getIP = require('../utils/getIP.js');
|
|||||||
* @property {string} UUID Unique identifier for the specific segment
|
* @property {string} UUID Unique identifier for the specific segment
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} Row
|
||||||
|
* @property {string} videoID
|
||||||
|
* @property {number} startTime
|
||||||
|
* @property {number} endTime
|
||||||
|
* @property {number} votes
|
||||||
|
* @property {string} UUID
|
||||||
|
* @property {string} category
|
||||||
|
* @property {number} shadowHidden
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input an array of database records and get only one back, weighed on votes.
|
||||||
|
* The logic is taken from getWeightedRandomChoice, just simplified input and output to not work on indices only.
|
||||||
|
*
|
||||||
|
* @param {Row[]} rows
|
||||||
|
* @returns {?Row}
|
||||||
|
*/
|
||||||
|
function pickWeightedRandomRow(rows) {
|
||||||
|
if (rows.length === 0) {
|
||||||
|
return null;
|
||||||
|
} else if (rows.length === 1) {
|
||||||
|
return rows[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const sqrtWeightsList = [];
|
||||||
|
let totalSqrtWeights = 0;
|
||||||
|
for (const row of rows) {
|
||||||
|
let sqrtVote = Math.sqrt((row.votes + 3) * 10);
|
||||||
|
sqrtWeightsList.push(sqrtVote);
|
||||||
|
totalSqrtWeights += sqrtVote;
|
||||||
|
}
|
||||||
|
|
||||||
|
const randomNumber = Math.random();
|
||||||
|
let currentVoteNumber = 0;
|
||||||
|
for (let i = 0; i < sqrtWeightsList.length; i++) {
|
||||||
|
if (randomNumber > currentVoteNumber / totalSqrtWeights && randomNumber < (currentVoteNumber + sqrtWeightsList[i]) / totalSqrtWeights) {
|
||||||
|
return rows[i];
|
||||||
|
}
|
||||||
|
currentVoteNumber += sqrtWeightsList[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* @param {string} prefix Lowercased hexadecimal hash prefix
|
* @param {string} prefix Lowercased hexadecimal hash prefix
|
||||||
* @param {string} hashedIP Custom hash of the visitor’s IP address
|
* @param {string} hashedIP Custom hash of the visitor’s IP address
|
||||||
* @returns {Segment[]}
|
* @returns {Object.<string, Segment[]>}
|
||||||
*/
|
*/
|
||||||
function getSkipSegmentsByHash(prefix, hashedIP) {
|
function getSkipSegmentsByHash(prefix, hashedIP) {
|
||||||
/**
|
/** @type Row[] */
|
||||||
* @constant
|
const rows = db.prepare('SELECT videoID, startTime, endTime, votes, UUID, category, shadowHidden FROM sponsorTimes WHERE votes >= -1 AND hashedVideoID LIKE ? ORDER BY videoID, startTime')
|
||||||
* @type {Segment[]}
|
|
||||||
* @default
|
|
||||||
*/
|
|
||||||
const segments = [];
|
|
||||||
|
|
||||||
const rows = db.prepare('SELECT videoID, startTime, endTime, UUID, category, shadowHidden FROM sponsorTimes WHERE votes >= -1 AND hashedVideoID LIKE ? ORDER BY startTime')
|
|
||||||
.all(prefix + '%');
|
.all(prefix + '%');
|
||||||
|
/** @type {string[]} */
|
||||||
const onlyForCurrentUser = privateDB.prepare('SELECT videoID FROM sponsorTimes WHERE hashedIP = ?').all(hashedIP).map(row => row.videoID);
|
const onlyForCurrentUser = privateDB.prepare('SELECT videoID FROM sponsorTimes WHERE hashedIP = ?').all(hashedIP).map(row => row.videoID);
|
||||||
|
/** @type {Object.<string, Segment[][]>} */
|
||||||
|
const rowGroupsPerVideo = {};
|
||||||
|
|
||||||
|
let previousVideoID = null;
|
||||||
|
let previousEndTime = null;
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
/** @TODO check if this logic does what is expected. */
|
/** @TODO check if this logic does what is expected. */
|
||||||
if (row.shadowHidden === 1 && onlyForCurrentUser.indexOf(row.videoID) === -1) {
|
if (row.shadowHidden === 1 && onlyForCurrentUser.indexOf(row.videoID) === -1) {
|
||||||
@@ -37,16 +77,30 @@ function getSkipSegmentsByHash(prefix, hashedIP) {
|
|||||||
// Do not send shadowHidden segments to them.
|
// Do not send shadowHidden segments to them.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// Split up the rows per video and group overlapping segments together.
|
||||||
segments.push({
|
if (!(row.videoID in rowGroupsPerVideo)) {
|
||||||
videoID: row.videoID,
|
rowGroupsPerVideo[row.videoID] = [];
|
||||||
segment: [row.startTime, row.endTime],
|
}
|
||||||
category: row.category,
|
if (previousVideoID === row.videoID && row.startTime <= previousEndTime) {
|
||||||
UUID: row.UUID
|
rowGroupsPerVideo[row.videoID][rowGroupsPerVideo[row.videoID].length - 1].push(row);
|
||||||
});
|
previousEndTime = Math.max(previousEndTime, row.endTime);
|
||||||
|
} else {
|
||||||
|
rowGroupsPerVideo[row.videoID].push([row]);
|
||||||
|
previousVideoID = row.videoID;
|
||||||
|
previousEndTime = row.endTime;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return segments;
|
/** @type {Object.<string, Segment[]>} */
|
||||||
|
const output = {};
|
||||||
|
for (const videoID in rowGroupsPerVideo) {
|
||||||
|
const pickedVideosForVideoID = [];
|
||||||
|
for (const group of rowGroupsPerVideo[videoID]) {
|
||||||
|
pickedVideosForVideoID.push(pickWeightedRandomRow(group));
|
||||||
|
}
|
||||||
|
output[videoID] = pickedVideosForVideoID.map(row => ({ videoID: row.videoID, segment: [row.startTime, row.endTime], category: row.category, UUID: row.UUID }));
|
||||||
|
}
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
const minimumPrefix = config.minimumPrefix || '3';
|
const minimumPrefix = config.minimumPrefix || '3';
|
||||||
|
|||||||
Reference in New Issue
Block a user