Merge branch 'master' of https://github.com/ajayyy/SponsorBlockServer into stricter-eslint

This commit is contained in:
Michael C 2021-07-13 15:55:03 -04:00
commit 5f2bc37e96
No known key found for this signature in database
GPG key ID: FFB04FB3B878B7B4
18 changed files with 145 additions and 48 deletions

View file

@ -13,11 +13,15 @@ export async function getLockCategories(req: Request, res: Response): Promise<Re
try {
// Get existing lock categories markers
const lockedCategories = await db.prepare("all", 'SELECT "category" from "lockCategories" where "videoID" = ?', [videoID]) as {category: Category}[];
if (lockedCategories.length === 0 || !lockedCategories[0]) return res.sendStatus(404);
// map to array in JS becaues of SQL incompatibilities
const categories = Object.values(lockedCategories).map((entry) => entry.category);
const row = await db.prepare("all", 'SELECT "category", "reason" from "lockCategories" where "videoID" = ?', [videoID]) as {category: Category, reason: string}[];
// map categories to array in JS becaues of SQL incompatibilities
const categories = row.map(item => item.category);
if (categories.length === 0 || !categories[0]) return res.sendStatus(404);
// Get longest lock reason
const reason = row.map(item => item.reason)
.reduce((a,b) => (a.length > b.length) ? a : b);
return res.send({
reason,
categories
});
} catch (err) {

View file

@ -7,13 +7,15 @@ import { Category, VideoID, VideoIDHash } from "../types/segments.model";
interface LockResultByHash {
videoID: VideoID,
hash: VideoIDHash,
reason: string,
categories: Category[]
}
interface DBLock {
videoID: VideoID,
hash: VideoIDHash,
category: Category
category: Category,
reason: string,
}
const mergeLocks = (source: DBLock[]) => {
@ -22,12 +24,15 @@ const mergeLocks = (source: DBLock[]) => {
// videoID already exists
const destMatch = dest.find(s => s.videoID === obj.videoID);
if (destMatch) {
// override longer reason
if (obj.reason.length > destMatch.reason.length) destMatch.reason = obj.reason;
// push to categories
destMatch.categories.push(obj.category);
} else {
dest.push({
videoID: obj.videoID,
hash: obj.hash,
reason: obj.reason,
categories: [obj.category]
});
}

View file

@ -275,6 +275,10 @@ async function chooseSegments(segments: DBSegment[], max: number): Promise<DBSeg
*/
async function handleGetSegments(req: Request, res: Response): Promise<Segment[] | false> {
const videoID = req.query.videoID as VideoID;
if (!videoID) {
res.status(400).send("videoID not specified");
return false;
}
// Default to sponsor
// If using params instead of JSON, only one category can be pulled
const categories: Category[] = req.query.categories

View file

@ -5,7 +5,7 @@ import { ActionType, Category, SegmentUUID, Service, VideoIDHash } from "../type
export async function getSkipSegmentsByHash(req: Request, res: Response): Promise<Response> {
let hashPrefix = req.params.prefix as VideoIDHash;
if (!hashPrefixTester(req.params.prefix)) {
if (!req.params.prefix || !hashPrefixTester(req.params.prefix)) {
return res.status(400).send("Hash prefix does not match format requirements."); // Exit early on faulty prefix
}
hashPrefix = hashPrefix.toLowerCase() as VideoIDHash;

View file

@ -6,6 +6,8 @@ import {getHash} from "../utils/getHash";
import { HashedUserID, UserID } from "../types/user.model";
export async function postWarning(req: Request, res: Response): Promise<Response> {
// exit early if no body passed in
if (!req.body.userID && !req.body.issuerUserID) return res.status(400).json({"message": "Missing parameters"});
// Collect user input data
const issuerUserID: HashedUserID = getHash(<UserID> req.body.issuerUserID);
const userID: UserID = req.body.userID;

View file

@ -10,9 +10,9 @@ export function skipSegmentsHashKey(hashedVideoIDPrefix: VideoIDHash, service: S
hashedVideoIDPrefix = hashedVideoIDPrefix.substring(0, 4) as VideoIDHash;
if (hashedVideoIDPrefix.length !== 4) Logger.warn(`Redis skip segment hash-prefix key is not length 4! ${hashedVideoIDPrefix}`);
return `segments.v2.${ service }.${ hashedVideoIDPrefix}`;
return `segments.v2.${service}.${hashedVideoIDPrefix}`;
}
export function reputationKey(userID: UserID): string {
return `reputation.user.${ userID}`;
return `reputation.user.${userID}`;
}

View file

@ -7,12 +7,14 @@ interface ReputationDBResult {
totalSubmissions: number,
downvotedSubmissions: number,
nonSelfDownvotedSubmissions: number,
upvotedSum: number,
votedSum: number,
lockedSum: number,
semiOldUpvotedSubmissions: number,
oldUpvotedSubmissions: number
}
export async function getReputation(userID: UserID): Promise<number> {
const weekAgo = Date.now() - 1000 * 60 * 60 * 24 * 45; // 45 days ago
const pastDate = Date.now() - 1000 * 60 * 60 * 24 * 45; // 45 days ago
// 1596240000000 is August 1st 2020, a little after auto upvote was disabled
const fetchFromDB = () => db.prepare("get",
@ -23,10 +25,11 @@ export async function getReputation(userID: UserID): Promise<number> {
WHERE b."userID" = ?
AND b."votes" > 0 AND b."category" = "a"."category" AND b."videoID" = "a"."videoID" LIMIT 1)
THEN 1 ELSE 0 END) AS "nonSelfDownvotedSubmissions",
SUM(CASE WHEN "votes" > 0 AND "timeSubmitted" > 1596240000000 THEN "votes" ELSE 0 END) AS "upvotedSum",
SUM(CASE WHEN "timeSubmitted" > 1596240000000 THEN "votes" ELSE 0 END) AS "votedSum",
SUM(locked) AS "lockedSum",
SUM(CASE WHEN "timeSubmitted" < ? AND "timeSubmitted" > 1596240000000 AND "votes" > 0 THEN 1 ELSE 0 END) AS "semiOldUpvotedSubmissions",
SUM(CASE WHEN "timeSubmitted" < ? AND "timeSubmitted" > 1596240000000 AND "votes" > 0 THEN 1 ELSE 0 END) AS "oldUpvotedSubmissions"
FROM "sponsorTimes" as "a" WHERE "userID" = ?`, [userID, pastDate, userID]) as Promise<ReputationDBResult>;
FROM "sponsorTimes" as "a" WHERE "userID" = ?`, [userID, weekAgo, pastDate, userID]) as Promise<ReputationDBResult>;
const result = await QueryCacher.get(fetchFromDB, reputationKey(userID));
@ -45,11 +48,19 @@ export async function getReputation(userID: UserID): Promise<number> {
return convertRange(Math.min(nonSelfDownvoteRatio, 0.4), 0.05, 0.4, -0.5, -2.5);
}
if (result.oldUpvotedSubmissions < 3 || result.upvotedSum < 5) {
if (result.votedSum < 5) {
return 0;
}
return convertRange(Math.min(result.upvotedSum, 150), 5, 150, 0, 7) + convertRange(Math.min(result.lockedSum ?? 0, 50), 0, 50, 0, 20);
if (result.oldUpvotedSubmissions < 3) {
if (result.semiOldUpvotedSubmissions > 3) {
return convertRange(Math.min(result.votedSum, 150), 5, 150, 0, 2) + convertRange(Math.min(result.lockedSum ?? 0, 50), 0, 50, 0, 5);
} else {
return 0;
}
}
return convertRange(Math.min(result.votedSum, 150), 5, 150, 0, 7) + convertRange(Math.min(result.lockedSum ?? 0, 50), 0, 50, 0, 20);
}
function convertRange(value: number, currentMin: number, currentMax: number, targetMin: number, targetMax: number): number {

View file

@ -10,19 +10,19 @@ describe("getLockCategories", () => {
const insertVipUserQuery = 'INSERT INTO "vipUsers" ("userID") VALUES (?)';
await db.prepare("run", insertVipUserQuery, [getHash("VIPUser-getLockCategories")]);
const insertLockCategoryQuery = 'INSERT INTO "lockCategories" ("userID", "videoID", "category") VALUES (?, ?, ?)';
await db.prepare("run", insertLockCategoryQuery, [getHash("VIPUser-getLockCategories"), "getLock-1", "sponsor"]);
await db.prepare("run", insertLockCategoryQuery, [getHash("VIPUser-getLockCategories"), "getLock-1", "interaction"]);
const insertLockCategoryQuery = 'INSERT INTO "lockCategories" ("userID", "videoID", "category", "reason") VALUES (?, ?, ?, ?)';
await db.prepare("run", insertLockCategoryQuery, [getHash("VIPUser-getLockCategories"), "getLock-1", "sponsor", "1-short"]);
await db.prepare("run", insertLockCategoryQuery, [getHash("VIPUser-getLockCategories"), "getLock-1", "interaction", "1-longer-reason"]);
await db.prepare("run", insertLockCategoryQuery, [getHash("VIPUser-getLockCategories"), "getLock-2", "preview"]);
await db.prepare("run", insertLockCategoryQuery, [getHash("VIPUser-getLockCategories"), "getLock-2", "preview", "2-reason"]);
await db.prepare("run", insertLockCategoryQuery, [getHash("VIPUser-getLockCategories"), "getLock-3", "nonmusic"]);
await db.prepare("run", insertLockCategoryQuery, [getHash("VIPUser-getLockCategories"), "getLock-3", "nonmusic", "3-reason"]);
});
it("Should update the database version when starting the application", async () => {
const version = (await db.prepare("get", "SELECT key, value FROM config where key = ?", ["version"])).value;
if (version > 1) return;
else return `Version isn't greater than 1. Version is ${version}`;
if (version > 20) return;
else return `Version isn't greater than 20. Version is ${version}`;
});
it("Should be able to get multiple locks", (done: Done) => {
@ -34,7 +34,8 @@ describe("getLockCategories", () => {
categories: [
"sponsor",
"interaction"
]
],
reason: "1-longer-reason"
};
assert.deepStrictEqual(data, expected);
done();
@ -50,7 +51,8 @@ describe("getLockCategories", () => {
const expected = {
categories: [
"preview"
]
],
reason: "2-reason"
};
assert.deepStrictEqual(data, expected);
done();

View file

@ -4,29 +4,28 @@ import {getHash} from "../../src/utils/getHash";
import {db} from "../../src/databases/databases";
import assert from "assert";
describe("getLockCategoriesByHash", () => {
before(async () => {
const insertVipUserQuery = 'INSERT INTO "vipUsers" ("userID") VALUES (?)';
await db.prepare("run", insertVipUserQuery, [getHash("VIPUser-getLockCategories")]);
const insertLockCategoryQuery = 'INSERT INTO "lockCategories" ("userID", "videoID", "category", "hashedVideoID") VALUES (?, ?, ?, ?)';
await db.prepare("run", insertLockCategoryQuery, [getHash("VIPUser-getLockCategories"), "getLockHash-1", "sponsor", "67a654898fda3a5541774aea345796c7709982bb6018cb08d22a18eeddccc1d0"]);
await db.prepare("run", insertLockCategoryQuery, [getHash("VIPUser-getLockCategories"), "getLockHash-1", "interaction", "67a654898fda3a5541774aea345796c7709982bb6018cb08d22a18eeddccc1d0"]);
const insertLockCategoryQuery = 'INSERT INTO "lockCategories" ("userID", "videoID", "category", "reason", "hashedVideoID") VALUES (?, ?, ?, ?, ?)';
await db.prepare("run", insertLockCategoryQuery, [getHash("VIPUser-getLockCategories"), "getLockHash-1", "sponsor", "1-reason-short", "67a654898fda3a5541774aea345796c7709982bb6018cb08d22a18eeddccc1d0"]);
await db.prepare("run", insertLockCategoryQuery, [getHash("VIPUser-getLockCategories"), "getLockHash-1", "interaction", "1-reason-longer", "67a654898fda3a5541774aea345796c7709982bb6018cb08d22a18eeddccc1d0"]);
await db.prepare("run", insertLockCategoryQuery, [getHash("VIPUser-getLockCategories"), "getLockHash-2", "preview", "dff09120437b4bd594dffae5f3cde3cfc5f6099fb01d0ef4051919b2908d9a50"]);
await db.prepare("run", insertLockCategoryQuery, [getHash("VIPUser-getLockCategories"), "getLockHash-2", "preview", "2-reason", "dff09120437b4bd594dffae5f3cde3cfc5f6099fb01d0ef4051919b2908d9a50"]);
await db.prepare("run", insertLockCategoryQuery, [getHash("VIPUser-getLockCategories"), "getLockHash-3", "nonmusic", "bf1b122fd5630e0df8626d00c4a95c58954ad715e5595b0f75a19ac131e28928"]);
await db.prepare("run", insertLockCategoryQuery, [getHash("VIPUser-getLockCategories"), "getLockHash-3", "nonmusic", "3-reason", "bf1b122fd5630e0df8626d00c4a95c58954ad715e5595b0f75a19ac131e28928"]);
await db.prepare("run", insertLockCategoryQuery, [getHash("VIPUser-getLockCategories"), "fakehash-1", "outro", "b05a20424f24a53dac1b059fb78d861ba9723645026be2174c93a94f9106bb35"]);
await db.prepare("run", insertLockCategoryQuery, [getHash("VIPUser-getLockCategories"), "fakehash-2", "intro", "b05acd1cd6ec7dffe5ffea64ada91ae7469d6db2ce21c7e30ad7fa62075d450"]);
await db.prepare("run", insertLockCategoryQuery, [getHash("VIPUser-getLockCategories"), "fakehash-2", "preview", "b05acd1cd6ec7dffe5ffea64ada91ae7469d6db2ce21c7e30ad7fa62075d450"]);
await db.prepare("run", insertLockCategoryQuery, [getHash("VIPUser-getLockCategories"), "fakehash-1", "outro", "fake1-reason", "b05a20424f24a53dac1b059fb78d861ba9723645026be2174c93a94f9106bb35"]);
await db.prepare("run", insertLockCategoryQuery, [getHash("VIPUser-getLockCategories"), "fakehash-2", "intro", "fake2-longer-reason", "b05acd1cd6ec7dffe5ffea64ada91ae7469d6db2ce21c7e30ad7fa62075d450"]);
await db.prepare("run", insertLockCategoryQuery, [getHash("VIPUser-getLockCategories"), "fakehash-2", "preview", "fake2-short", "b05acd1cd6ec7dffe5ffea64ada91ae7469d6db2ce21c7e30ad7fa62075d450"]);
});
it("Database should be greater or equal to version 18", async () => {
it("Database should be greater or equal to version 20", async () => {
const version = (await db.prepare("get", "SELECT key, value FROM config where key = ?", ["version"])).value;
if (version >= 18) return;
else return `Version isn't greater than 18. Version is ${version}`;
if (version >= 20) return;
else return `Version isn't greater than 20. Version is ${version}`;
});
it("Should be able to get multiple locks in one object", (done: Done) => {
@ -40,7 +39,8 @@ describe("getLockCategoriesByHash", () => {
categories: [
"sponsor",
"interaction"
]
],
reason: "1-reason-longer"
}];
assert.deepStrictEqual(data, expected);
done();
@ -58,7 +58,8 @@ describe("getLockCategoriesByHash", () => {
hash: getHash("getLockHash-2", 1),
categories: [
"preview"
]
],
reason: "2-reason"
}];
assert.deepStrictEqual(data, expected);
done();
@ -76,7 +77,8 @@ describe("getLockCategoriesByHash", () => {
hash: getHash("getLockHash-3", 1),
categories: [
"nonmusic"
]
],
reason: "3-reason"
}];
assert.deepStrictEqual(data, expected);
done();
@ -94,14 +96,16 @@ describe("getLockCategoriesByHash", () => {
hash: "b05a20424f24a53dac1b059fb78d861ba9723645026be2174c93a94f9106bb35",
categories: [
"outro"
]
],
reason: "fake1-reason"
}, {
videoID: "fakehash-2",
hash: "b05acd1cd6ec7dffe5ffea64ada91ae7469d6db2ce21c7e30ad7fa62075d450",
categories: [
"intro",
"preview"
]
],
reason: "fake2-longer-reason"
}];
assert.deepStrictEqual(data, expected);
done();
@ -144,4 +148,22 @@ describe("getLockCategoriesByHash", () => {
})
.catch(err => done(err));
});
it("should return 400 if hash too short", (done: Done) => {
fetch(`${getbaseURL()}/api/lockCategories/00`)
.then(res => {
assert.strictEqual(res.status, 400);
done();
})
.catch(err => done(err));
});
it("should return 400 if no hash specified", (done: Done) => {
fetch(`${getbaseURL()}/api/lockCategories/`)
.then(res => {
assert.strictEqual(res.status, 400);
done();
})
.catch(err => done(err));
});
});

View file

@ -340,4 +340,13 @@ describe("getSkipSegments", () => {
})
.catch(err => done(err));
});
it('Should get 400 if no videoID passed in', (done: Done) => {
fetch(getbaseURL() + '/api/skipSegments')
.then(async res => {
assert.strictEqual(res.status, 400);
done();
})
.catch(err => done(err));
});
});

View file

@ -11,7 +11,7 @@ const mockManager = ImportMock.mockStaticClass(YouTubeAPIModule, "YouTubeAPI");
const sinonStub = mockManager.mock("listVideos");
sinonStub.callsFake(YouTubeApiMock.listVideos);
describe("getSegmentsByHash", () => {
describe("getSkipSegmentsByHash", () => {
before(async () => {
const query = 'INSERT INTO "sponsorTimes" ("videoID", "startTime", "endTime", "votes", "UUID", "userID", "timeSubmitted", views, category, "actionType", "service", "hidden", "shadowHidden", "hashedVideoID") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
await db.prepare("run", query, ["getSegmentsByHash-0", 1, 10, 2, "getSegmentsByHash-0-0", "testman", 0, 50, "sponsor", "skip", "YouTube", 0, 0, "fdaff4dee1043451faa7398324fb63d8618ebcd11bddfe0491c488db12c6c910"]);
@ -88,7 +88,7 @@ describe("getSegmentsByHash", () => {
it("Should return 400 prefix too long", (done: Done) => {
const prefix = "1".repeat(50);
assert.ok(prefix.length > 33, "failed to generate long enough string");
fetch(`${getbaseURL()}/api/skipSegments/${ prefix }?categories=["shilling"]`)
fetch(`${getbaseURL()}/api/skipSegments/${prefix}?categories=["shilling"]`)
.then(res => {
assert.strictEqual(res.status, 400);
done();

View file

@ -313,4 +313,13 @@ describe("getUserID", () => {
})
.catch(err => done(err));
});
it('should return 400 if no username parameter specified', (done: Done) => {
fetch(getbaseURL() + '/api/userID')
.then(res => {
assert.strictEqual(res.status, 400);
done();
})
.catch(() => ("couldn't call endpoint"));
});
});

View file

@ -79,7 +79,7 @@ describe("getUserInfo", () => {
});
it("Should get warning data with public ID", (done: Done) => {
fetch(`${getbaseURL()}/api/userInfo?publicUserID=${ getHash("getuserinfo_warning_0")}`)
fetch(`${getbaseURL()}/api/userInfo?publicUserID=${getHash("getuserinfo_warning_0")}`)
.then(async res => {
assert.strictEqual(res.status, 200);
const data = await res.json();

View file

@ -81,4 +81,18 @@ describe("postPurgeAllSegments", function () {
})
.catch(err => done(err));
});
it('Should return 400 if missing body', function (done: Done) {
fetch(`${baseURL}${route}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
})
.then(async res => {
assert.strictEqual(res.status, 400);
done();
})
.catch(err => done(err));
});
});

View file

@ -610,7 +610,7 @@ describe("postSkipSegments", () => {
done(); // success
} else {
const body = await res.text();
done(`Status code was ${ res.status } ${ body}`);
done(`Status code was ${res.status} ${body}`);
}
})
.catch(err => done(err));
@ -636,7 +636,7 @@ describe("postSkipSegments", () => {
done(); // success
} else {
const body = await res.text();
done(`Status code was ${ res.status } ${ body}`);
done(`Status code was ${res.status} ${body}`);
}
})
.catch(err => done(err));

View file

@ -98,4 +98,19 @@ describe("postWarning", () => {
})
.catch(err => done(err));
});
it('Should return 400 if missing body', (done: Done) => {
fetch(getbaseURL()
+ "/api/warnUser", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
})
.then(async res => {
assert.strictEqual(res.status, 400);
done();
})
.catch(err => done(err));
});
});

View file

@ -114,11 +114,11 @@ describe("reputation", () => {
});
it("user with high reputation", async () => {
assert.strictEqual(await getReputation(getHash(userIDHighRep)), 0.24137931034482757);
assert.strictEqual(await getReputation(getHash(userIDHighRep)), 0.19310344827586207);
});
it("user with high reputation and locked segments", async () => {
assert.strictEqual(await getReputation(getHash(userIDHighRepAndLocked)), 1.8413793103448277);
assert.strictEqual(await getReputation(getHash(userIDHighRepAndLocked)), 1.793103448275862);
});
});

View file

@ -235,7 +235,7 @@ describe("voteOnSponsorTime", () => {
assert.strictEqual(sponsorVotes, 1);
done();
} else {
done(`Status code was ${ res.status}`);
done(`Status code was ${res.status}`);
}
})
.catch(err => done(err));
@ -245,7 +245,7 @@ describe("voteOnSponsorTime", () => {
it("Should not be able to change your vote to an invalid category", (done: Done) => {
const vote = (inputCat: string, assertCat: string, callback: Done) => {
fetch(`${getbaseURL()
}/api/voteOnSponsorTime?userID=randomID2&UUID=incorrect-category-change&category=${ inputCat}`)
}/api/voteOnSponsorTime?userID=randomID2&UUID=incorrect-category-change&category=${inputCat}`)
.then(async () => {
const row = await db.prepare("get", `SELECT "category" FROM "sponsorTimes" WHERE "UUID" = ?`, ["incorrect-category-change"]);
assert.strictEqual(row.category, assertCat);