mirror of
https://github.com/ajayyy/SponsorBlockServer.git
synced 2025-12-06 03:26:59 +03:00
add addUserAsTempVIP
This commit is contained in:
@@ -207,6 +207,7 @@
|
|||||||
[categoryVotes](#categoryVotes)
|
[categoryVotes](#categoryVotes)
|
||||||
[sponsorTimes](#sponsorTimes)
|
[sponsorTimes](#sponsorTimes)
|
||||||
[config](#config)
|
[config](#config)
|
||||||
|
[tempVipLog](#tempVipLog)
|
||||||
|
|
||||||
### vote
|
### vote
|
||||||
|
|
||||||
@@ -270,3 +271,11 @@
|
|||||||
| index | field |
|
| index | field |
|
||||||
| -- | :--: |
|
| -- | :--: |
|
||||||
| ratings_videoID | videoID, service, userID, timeSubmitted |
|
| ratings_videoID | videoID, service, userID, timeSubmitted |
|
||||||
|
|
||||||
|
### tempVipLog
|
||||||
|
| Name | Type | |
|
||||||
|
| -- | :--: | -- |
|
||||||
|
| issuerUserID | TEXT | not null |
|
||||||
|
| targetUserID | TEXT | not null |
|
||||||
|
| enabled | BOOLEAN | not null |
|
||||||
|
| updatedAt | INTEGER | not null |
|
||||||
@@ -5,7 +5,7 @@ CREATE TABLE IF NOT EXISTS "ratings" (
|
|||||||
"service" TEXT NOT NULL default 'YouTube',
|
"service" TEXT NOT NULL default 'YouTube',
|
||||||
"type" INTEGER NOT NULL,
|
"type" INTEGER NOT NULL,
|
||||||
"userID" TEXT NOT NULL,
|
"userID" TEXT NOT NULL,
|
||||||
"timeSubmitted" INTEGER NOT NULL,
|
"timeSubmitted" INTEGER NOT NULL,
|
||||||
"hashedIP" TEXT NOT NULL
|
"hashedIP" TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
12
databases/_upgrade_private_5.sql
Normal file
12
databases/_upgrade_private_5.sql
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS "tempVipLog" (
|
||||||
|
"issuerUserID" TEXT NOT NULL,
|
||||||
|
"targetUserID" TEXT NOT NULL,
|
||||||
|
"enabled" BOOLEAN NOT NULL,
|
||||||
|
"updatedAt" INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
UPDATE "config" SET value = 5 WHERE key = 'version';
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -46,6 +46,7 @@ import { getChapterNames } from "./routes/getChapterNames";
|
|||||||
import { postRating } from "./routes/ratings/postRating";
|
import { postRating } from "./routes/ratings/postRating";
|
||||||
import { getRating } from "./routes/ratings/getRating";
|
import { getRating } from "./routes/ratings/getRating";
|
||||||
import { postClearCache as ratingPostClearCache } from "./routes/ratings/postClearCache";
|
import { postClearCache as ratingPostClearCache } from "./routes/ratings/postClearCache";
|
||||||
|
import { addUserAsTempVIP } from "./routes/addUserAsTempVIP";
|
||||||
|
|
||||||
export function createServer(callback: () => void): Server {
|
export function createServer(callback: () => void): Server {
|
||||||
// Create a service (the app object is just a callback).
|
// Create a service (the app object is just a callback).
|
||||||
@@ -117,6 +118,8 @@ function setupRoutes(router: Router) {
|
|||||||
|
|
||||||
//Endpoint used to make a user a VIP user with special privileges
|
//Endpoint used to make a user a VIP user with special privileges
|
||||||
router.post("/api/addUserAsVIP", addUserAsVIP);
|
router.post("/api/addUserAsVIP", addUserAsVIP);
|
||||||
|
//Endpoint to add a user as a temporary VIP
|
||||||
|
router.post("/api/addUserAsTempVIP", addUserAsTempVIP);
|
||||||
|
|
||||||
//Gets all the views added up for one userID
|
//Gets all the views added up for one userID
|
||||||
//Useful to see how much one user has contributed
|
//Useful to see how much one user has contributed
|
||||||
|
|||||||
71
src/routes/addUserAsTempVIP.ts
Normal file
71
src/routes/addUserAsTempVIP.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import { VideoID } from "../types/segments.model";
|
||||||
|
import { YouTubeAPI } from "../utils/youtubeApi";
|
||||||
|
import { APIVideoInfo } from "../types/youtubeApi.model";
|
||||||
|
import { config } from "../config";
|
||||||
|
import { getHashCache } from "../utils/getHashCache";
|
||||||
|
import { privateDB } from "../databases/databases";
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
import { isUserVIP } from "../utils/isUserVIP";
|
||||||
|
import { HashedUserID } from "../types/user.model";
|
||||||
|
import redis from "../utils/redis";
|
||||||
|
import { tempVIPKey } from "../utils/redisKeys";
|
||||||
|
|
||||||
|
interface AddUserAsTempVIPRequest extends Request {
|
||||||
|
query: {
|
||||||
|
userID: HashedUserID;
|
||||||
|
adminUserID: string;
|
||||||
|
enabled: string;
|
||||||
|
channelVideoID: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getYouTubeVideoInfo(videoID: VideoID, ignoreCache = false): Promise<APIVideoInfo> {
|
||||||
|
return (config.newLeafURLs) ? YouTubeAPI.listVideos(videoID, ignoreCache) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getChannelInfo = async (videoID: VideoID): Promise<{id: string | null, name: string | null }> => {
|
||||||
|
const videoInfo = await getYouTubeVideoInfo(videoID);
|
||||||
|
return {
|
||||||
|
id: videoInfo?.data?.authorId,
|
||||||
|
name: videoInfo?.data?.author
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function addUserAsTempVIP(req: AddUserAsTempVIPRequest, res: Response): Promise<Response> {
|
||||||
|
const { query: { userID, adminUserID } } = req;
|
||||||
|
|
||||||
|
const enabled = req.query?.enabled === "true";
|
||||||
|
const channelVideoID = req.query?.channelVideoID as VideoID;
|
||||||
|
|
||||||
|
if (!userID || !adminUserID || !channelVideoID ) {
|
||||||
|
// invalid request
|
||||||
|
return res.sendStatus(400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// hash the issuer userID
|
||||||
|
const issuerUserID = await getHashCache(adminUserID);
|
||||||
|
// check if issuer is VIP
|
||||||
|
const issuerIsVIP = await isUserVIP(issuerUserID as HashedUserID);
|
||||||
|
if (!issuerIsVIP) {
|
||||||
|
return res.sendStatus(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check to see if this user is already a vip
|
||||||
|
const targetIsVIP = await isUserVIP(userID);
|
||||||
|
if (targetIsVIP) {
|
||||||
|
return res.sendStatus(409);
|
||||||
|
}
|
||||||
|
|
||||||
|
const startTime = Date.now();
|
||||||
|
const dayInSeconds = 86400;
|
||||||
|
const channelInfo = await getChannelInfo(channelVideoID);
|
||||||
|
|
||||||
|
await privateDB.prepare("run", `INSERT INTO "tempVipLog" VALUES (?, ?, ?, ?)`, [adminUserID, userID, + enabled, startTime]);
|
||||||
|
if (enabled) { // add to redis
|
||||||
|
await redis.setAsyncEx(tempVIPKey(userID), channelInfo?.id, dayInSeconds);
|
||||||
|
} else { // delete key
|
||||||
|
await redis.delAsync(tempVIPKey(userID));
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.sendStatus(200);
|
||||||
|
}
|
||||||
@@ -322,7 +322,7 @@ export async function voteOnSponsorTime(req: Request, res: Response): Promise<Re
|
|||||||
|
|
||||||
//check if this user is on the vip list
|
//check if this user is on the vip list
|
||||||
const videoID = await db.prepare("get", `select "videoID" from "sponsorTimes" where "UUID" = ?`, [UUID]);
|
const videoID = await db.prepare("get", `select "videoID" from "sponsorTimes" where "UUID" = ?`, [UUID]);
|
||||||
const isTempVIP = await isUserTempVIP(nonAnonUserID, videoID);
|
const isTempVIP = await isUserTempVIP(nonAnonUserID, videoID?.videoID || null);
|
||||||
const isVIP = await isUserVIP(nonAnonUserID) || isTempVIP;
|
const isVIP = await isUserVIP(nonAnonUserID) || isTempVIP;
|
||||||
|
|
||||||
//check if user voting on own submission
|
//check if user voting on own submission
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ interface RedisSB {
|
|||||||
getAsync?(key: string): Promise<{err: Error | null, reply: string | null}>;
|
getAsync?(key: string): Promise<{err: Error | null, reply: string | null}>;
|
||||||
set(key: string, value: string, callback?: Callback<string | null>): void;
|
set(key: string, value: string, callback?: Callback<string | null>): void;
|
||||||
setAsync?(key: string, value: string): Promise<{err: Error | null, reply: string | null}>;
|
setAsync?(key: string, value: string): Promise<{err: Error | null, reply: string | null}>;
|
||||||
|
setAsyncEx?(key: string, value: string, seconds: number): Promise<{err: Error | null, reply: string | null}>;
|
||||||
delAsync?(...keys: [string]): Promise<Error | null>;
|
delAsync?(...keys: [string]): Promise<Error | null>;
|
||||||
close?(flush?: boolean): void;
|
close?(flush?: boolean): void;
|
||||||
}
|
}
|
||||||
@@ -18,6 +19,8 @@ let exportObject: RedisSB = {
|
|||||||
set: (key, value, callback) => callback(null, undefined),
|
set: (key, value, callback) => callback(null, undefined),
|
||||||
setAsync: () =>
|
setAsync: () =>
|
||||||
new Promise((resolve) => resolve({ err: null, reply: undefined })),
|
new Promise((resolve) => resolve({ err: null, reply: undefined })),
|
||||||
|
setAsyncEx: () =>
|
||||||
|
new Promise((resolve) => resolve({ err: null, reply: undefined })),
|
||||||
delAsync: () =>
|
delAsync: () =>
|
||||||
new Promise((resolve) => resolve(null)),
|
new Promise((resolve) => resolve(null)),
|
||||||
};
|
};
|
||||||
@@ -29,6 +32,7 @@ if (config.redis) {
|
|||||||
|
|
||||||
exportObject.getAsync = (key) => new Promise((resolve) => client.get(key, (err, reply) => resolve({ err, reply })));
|
exportObject.getAsync = (key) => new Promise((resolve) => client.get(key, (err, reply) => resolve({ err, reply })));
|
||||||
exportObject.setAsync = (key, value) => new Promise((resolve) => client.set(key, value, (err, reply) => resolve({ err, reply })));
|
exportObject.setAsync = (key, value) => new Promise((resolve) => client.set(key, value, (err, reply) => resolve({ err, reply })));
|
||||||
|
exportObject.setAsyncEx = (key, value, seconds) => new Promise((resolve) => client.setex(key, seconds, value, (err, reply) => resolve({ err, reply })));
|
||||||
exportObject.delAsync = (...keys) => new Promise((resolve) => client.del(keys, (err) => resolve(err)));
|
exportObject.delAsync = (...keys) => new Promise((resolve) => client.del(keys, (err) => resolve(err)));
|
||||||
exportObject.close = (flush) => client.end(flush);
|
exportObject.close = (flush) => client.end(flush);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Service, VideoID, VideoIDHash } from "../types/segments.model";
|
import { Service, VideoID, VideoIDHash } from "../types/segments.model";
|
||||||
import { UserID } from "../types/user.model";
|
import { HashedUserID, UserID } from "../types/user.model";
|
||||||
import { HashedValue } from "../types/hash.model";
|
import { HashedValue } from "../types/hash.model";
|
||||||
import { Logger } from "./logger";
|
import { Logger } from "./logger";
|
||||||
|
|
||||||
@@ -32,5 +32,5 @@ export function shaHashKey(singleIter: HashedValue): string {
|
|||||||
return `sha.hash.${singleIter}`;
|
return `sha.hash.${singleIter}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const tempVIPKey = (userID: UserID): string =>
|
export const tempVIPKey = (userID: HashedUserID): string =>
|
||||||
`vip.temp.${userID}`;
|
`vip.temp.${userID}`;
|
||||||
165
test/cases/tempVip.ts
Normal file
165
test/cases/tempVip.ts
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import { config } from "../../src/config";
|
||||||
|
import { getHash } from "../../src/utils/getHash";
|
||||||
|
import { tempVIPKey } from "../../src/utils/redisKeys";
|
||||||
|
import { HashedUserID } from "../../src/types/user.model";
|
||||||
|
import { client } from "../utils/httpClient";
|
||||||
|
import { db, privateDB } from "../../src/databases/databases";
|
||||||
|
import redis from "../../src/utils/redis";
|
||||||
|
import assert from "assert";
|
||||||
|
|
||||||
|
// helpers
|
||||||
|
const getSegment = (UUID: string) => db.prepare("get", `SELECT "votes", "locked", "category" FROM "sponsorTimes" WHERE "UUID" = ?`, [UUID]);
|
||||||
|
|
||||||
|
const permVIP = "tempVipPermOne";
|
||||||
|
const publicPermVIP = getHash(permVIP) as HashedUserID;
|
||||||
|
const tempVIPOne = "tempVipTempOne";
|
||||||
|
const publicTempVIPOne = getHash(tempVIPOne) as HashedUserID;
|
||||||
|
const UUID0 = "tempvip-uuid0";
|
||||||
|
const UUID1 = "tempvip-uuid1";
|
||||||
|
|
||||||
|
const tempVIPEndpoint = "/api/addUserAsTempVIP";
|
||||||
|
const addTempVIP = (enabled: boolean) => client({
|
||||||
|
url: tempVIPEndpoint,
|
||||||
|
method: "POST",
|
||||||
|
params: {
|
||||||
|
userID: publicTempVIPOne,
|
||||||
|
adminUserID: permVIP,
|
||||||
|
channelVideoID: "channelid-convert",
|
||||||
|
enabled: enabled
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const voteEndpoint = "/api/voteOnSponsorTime";
|
||||||
|
const postVote = (userID: string, UUID: string, type: number) => client({
|
||||||
|
method: "POST",
|
||||||
|
url: voteEndpoint,
|
||||||
|
params: {
|
||||||
|
userID,
|
||||||
|
UUID,
|
||||||
|
type
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const postVoteCategory = (userID: string, UUID: string, category: string) => client({
|
||||||
|
method: "POST",
|
||||||
|
url: voteEndpoint,
|
||||||
|
params: {
|
||||||
|
userID,
|
||||||
|
UUID,
|
||||||
|
category
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const checkUserVIP = async () => {
|
||||||
|
const { reply } = await redis.getAsync(tempVIPKey(publicTempVIPOne));
|
||||||
|
return reply;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("tempVIP test", function() {
|
||||||
|
before(async function() {
|
||||||
|
if (!config.redis) this.skip();
|
||||||
|
|
||||||
|
const insertSponsorTimeQuery = 'INSERT INTO "sponsorTimes" ("videoID", "startTime", "endTime", "votes", "locked", "UUID", "userID", "timeSubmitted", "views", "category", "shadowHidden") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
|
||||||
|
await db.prepare("run", insertSponsorTimeQuery, ["channelid-convert", 0, 1, 0, 0, UUID0, "testman", 0, 50, "sponsor", 0]);
|
||||||
|
await db.prepare("run", insertSponsorTimeQuery, ["channelid-convert", 1, 9, 0, 1, "tempvip-submit", publicTempVIPOne, 0, 50, "sponsor", 0]);
|
||||||
|
await db.prepare("run", insertSponsorTimeQuery, ["otherchannel", 1, 9, 0, 1, UUID1, "testman", 0, 50, "sponsor", 0]);
|
||||||
|
|
||||||
|
|
||||||
|
await db.prepare("run", 'INSERT INTO "vipUsers" ("userID") VALUES (?)', [publicPermVIP]);
|
||||||
|
// clear redis if running consecutive tests
|
||||||
|
await redis.delAsync(tempVIPKey(publicTempVIPOne));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should update db version when starting the application", () => {
|
||||||
|
privateDB.prepare("get", "SELECT key, value FROM config where key = ?", ["version"])
|
||||||
|
.then(row => {
|
||||||
|
assert.ok(row.value >= 5, `Versions are not at least 5. private is ${row.value}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("User should not already be temp VIP", (done) => {
|
||||||
|
checkUserVIP()
|
||||||
|
.then(result => {
|
||||||
|
assert.ok(!result);
|
||||||
|
done(result);
|
||||||
|
})
|
||||||
|
.catch(err => done(err));
|
||||||
|
});
|
||||||
|
it("Should be able to normal upvote as a user", (done) => {
|
||||||
|
postVote(tempVIPOne, UUID0, 1)
|
||||||
|
.then(async res => {
|
||||||
|
assert.strictEqual(res.status, 200);
|
||||||
|
const row = await getSegment(UUID0);
|
||||||
|
assert.strictEqual(row.votes, 1);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(err => done(err));
|
||||||
|
});
|
||||||
|
it("Should be able to add tempVIP", (done) => {
|
||||||
|
addTempVIP(true)
|
||||||
|
.then(async res => {
|
||||||
|
assert.strictEqual(res.status, 200);
|
||||||
|
const vip = await checkUserVIP();
|
||||||
|
assert.ok(vip == "ChannelID");
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(err => done(err));
|
||||||
|
});
|
||||||
|
it("Should be able to VIP downvote", (done) => {
|
||||||
|
postVote(tempVIPOne, UUID0, 0)
|
||||||
|
.then(async res => {
|
||||||
|
assert.strictEqual(res.status, 200);
|
||||||
|
const row = await getSegment(UUID0);
|
||||||
|
assert.strictEqual(row.votes, -2);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(err => done(err));
|
||||||
|
});
|
||||||
|
it("Should be able to VIP lock", (done) => {
|
||||||
|
postVote(tempVIPOne, UUID0, 1)
|
||||||
|
.then(async res => {
|
||||||
|
assert.strictEqual(res.status, 200);
|
||||||
|
const row = await getSegment(UUID0);
|
||||||
|
assert.ok(row.votes > -2);
|
||||||
|
assert.strictEqual(row.locked, 1);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(err => done(err));
|
||||||
|
});
|
||||||
|
it("Should be able to VIP change category", (done) => {
|
||||||
|
postVoteCategory(tempVIPOne, UUID0, "filler")
|
||||||
|
.then(async res => {
|
||||||
|
assert.strictEqual(res.status, 200);
|
||||||
|
const row = await getSegment(UUID0);
|
||||||
|
assert.strictEqual(row.category, "filler");
|
||||||
|
assert.strictEqual(row.locked, 1);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(err => done(err));
|
||||||
|
});
|
||||||
|
it("Should be able to remove tempVIP prematurely", (done) => {
|
||||||
|
addTempVIP(false)
|
||||||
|
.then(async res => {
|
||||||
|
assert.strictEqual(res.status, 200);
|
||||||
|
const vip = await checkUserVIP();
|
||||||
|
done(vip);
|
||||||
|
})
|
||||||
|
.catch(err => done(err));
|
||||||
|
});
|
||||||
|
it("Should not be able to VIP downvote", (done) => {
|
||||||
|
postVote(tempVIPOne, UUID1, 0)
|
||||||
|
.then(async res => {
|
||||||
|
assert.strictEqual(res.status, 200);
|
||||||
|
const row = await getSegment(UUID1);
|
||||||
|
assert.strictEqual(row.votes, 0);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(err => done(err));
|
||||||
|
});
|
||||||
|
it("Should not be able to VIP change category", (done) => {
|
||||||
|
postVoteCategory(tempVIPOne, UUID1, "filler")
|
||||||
|
.then(async res => {
|
||||||
|
assert.strictEqual(res.status, 200);
|
||||||
|
const row = await getSegment(UUID1);
|
||||||
|
assert.strictEqual(row.category, "sponsor");
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(err => done(err));
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -47,6 +47,15 @@ export class YouTubeApiMock {
|
|||||||
]
|
]
|
||||||
} as APIVideoData
|
} as APIVideoData
|
||||||
};
|
};
|
||||||
|
} else if (obj.id === "channelid-convert") {
|
||||||
|
return {
|
||||||
|
err: null,
|
||||||
|
data: {
|
||||||
|
title: "Video Lookup Title",
|
||||||
|
author: "ChannelAuthor",
|
||||||
|
authorId: "ChannelID"
|
||||||
|
} as APIVideoData
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
err: null,
|
err: null,
|
||||||
|
|||||||
Reference in New Issue
Block a user