mirror of
https://github.com/ajayyy/SponsorBlockServer.git
synced 2025-12-19 14:09:06 +03:00
migrate to typescript
This commit is contained in:
committed by
Dainius Dauksevicius
parent
c462323dd5
commit
08d27265fc
@@ -1,44 +0,0 @@
|
||||
module.exports = function createMemoryCache(memoryFn, cacheTimeMs) {
|
||||
if (isNaN(cacheTimeMs)) cacheTimeMs = 0;
|
||||
|
||||
// holds the promise results
|
||||
const cache = new Map();
|
||||
// holds the promises that are not fulfilled
|
||||
const promiseMemory = new Map();
|
||||
return (...args) => {
|
||||
// create cacheKey by joining arguments as string
|
||||
const cacheKey = args.join('.');
|
||||
// check if promising is already running
|
||||
if (promiseMemory.has(cacheKey)) {
|
||||
return promiseMemory.get(cacheKey);
|
||||
}
|
||||
else {
|
||||
// check if result is in cache
|
||||
if (cache.has(cacheKey)) {
|
||||
const cacheItem = cache.get(cacheKey);
|
||||
const now = Date.now();
|
||||
// check if cache is valid
|
||||
if (!(cacheItem.cacheTime + cacheTimeMs < now)) {
|
||||
return Promise.resolve(cacheItem.result);
|
||||
}
|
||||
}
|
||||
// create new promise
|
||||
const promise = new Promise(async (resolve, reject) => {
|
||||
resolve((await memoryFn(...args)));
|
||||
});
|
||||
// store promise reference until fulfilled
|
||||
promiseMemory.set(cacheKey, promise);
|
||||
return promise.then(result => {
|
||||
// store promise result in cache
|
||||
cache.set(cacheKey, {
|
||||
result,
|
||||
cacheTime: Date.now(),
|
||||
});
|
||||
// remove fulfilled promise from memory
|
||||
promiseMemory.delete(cacheKey);
|
||||
// return promise result
|
||||
return result;
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
43
src/utils/createMemoryCache.ts
Normal file
43
src/utils/createMemoryCache.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
export function createMemoryCache(memoryFn: (...args: any[]) => void, cacheTimeMs: number) {
|
||||
if (isNaN(cacheTimeMs)) cacheTimeMs = 0;
|
||||
|
||||
// holds the promise results
|
||||
const cache = new Map();
|
||||
// holds the promises that are not fulfilled
|
||||
const promiseMemory = new Map();
|
||||
return (...args: any[]) => {
|
||||
// create cacheKey by joining arguments as string
|
||||
const cacheKey = args.join('.');
|
||||
// check if promising is already running
|
||||
if (promiseMemory.has(cacheKey)) {
|
||||
return promiseMemory.get(cacheKey);
|
||||
} else {
|
||||
// check if result is in cache
|
||||
if (cache.has(cacheKey)) {
|
||||
const cacheItem = cache.get(cacheKey);
|
||||
const now = Date.now();
|
||||
// check if cache is valid
|
||||
if (!(cacheItem.cacheTime + cacheTimeMs < now)) {
|
||||
return Promise.resolve(cacheItem.result);
|
||||
}
|
||||
}
|
||||
// create new promise
|
||||
const promise = new Promise(async (resolve) => {
|
||||
resolve((await memoryFn(...args)));
|
||||
});
|
||||
// store promise reference until fulfilled
|
||||
promiseMemory.set(cacheKey, promise);
|
||||
return promise.then(result => {
|
||||
// store promise result in cache
|
||||
cache.set(cacheKey, {
|
||||
result,
|
||||
cacheTime: Date.now(),
|
||||
});
|
||||
// remove fulfilled promise from memory
|
||||
promiseMemory.delete(cacheKey);
|
||||
// return promise result
|
||||
return result;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
//converts time in seconds to minutes:seconds
|
||||
module.exports = function getFormattedTime(totalSeconds) {
|
||||
let minutes = Math.floor(totalSeconds / 60);
|
||||
let seconds = totalSeconds - minutes * 60;
|
||||
let secondsDisplay = seconds.toFixed(3);
|
||||
if (seconds < 10) {
|
||||
//add a zero
|
||||
secondsDisplay = "0" + secondsDisplay;
|
||||
}
|
||||
|
||||
let formatted = minutes+ ":" + secondsDisplay;
|
||||
|
||||
return formatted;
|
||||
}
|
||||
14
src/utils/getFormattedTime.ts
Normal file
14
src/utils/getFormattedTime.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Converts time in seconds to minutes:seconds
|
||||
*/
|
||||
export function getFormattedTime(totalSeconds: number) {
|
||||
let minutes = Math.floor(totalSeconds / 60);
|
||||
let seconds = totalSeconds - minutes * 60;
|
||||
let secondsDisplay = seconds.toFixed(3);
|
||||
if (seconds < 10) {
|
||||
//add a zero
|
||||
secondsDisplay = '0' + secondsDisplay;
|
||||
}
|
||||
|
||||
return minutes + ':' + secondsDisplay;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
var crypto = require('crypto');
|
||||
|
||||
module.exports = function (value, times=5000) {
|
||||
if (times <= 0) return "";
|
||||
|
||||
for (let i = 0; i < times; i++) {
|
||||
let hashCreator = crypto.createHash('sha256');
|
||||
value = hashCreator.update(value).digest('hex');
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
12
src/utils/getHash.ts
Normal file
12
src/utils/getHash.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import crypto from 'crypto';
|
||||
|
||||
export function getHash(value: string, times = 5000) {
|
||||
if (times <= 0) return "";
|
||||
|
||||
for (let i = 0; i < times; i++) {
|
||||
let hashCreator = crypto.createHash('sha256');
|
||||
value = hashCreator.update(value).digest('hex');
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
var config = require('../config.js');
|
||||
|
||||
module.exports = function getIP(req) {
|
||||
if (config.behindProxy === true || config.behindProxy === "true") config.behindProxy = "X-Forwarded-For";
|
||||
|
||||
switch (config.behindProxy) {
|
||||
case "X-Forwarded-For":
|
||||
return req.headers['x-forwarded-for'];
|
||||
case "Cloudflare":
|
||||
return req.headers['cf-connecting-ip'];
|
||||
case "X-Real-IP":
|
||||
return req.headers['x-real-ip'];
|
||||
default:
|
||||
return req.connection.remoteAddress;
|
||||
}
|
||||
}
|
||||
19
src/utils/getIP.ts
Normal file
19
src/utils/getIP.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import {config} from '../config';
|
||||
import {Request} from 'express';
|
||||
|
||||
export function getIP(req: Request): string {
|
||||
if (config.behindProxy === true || config.behindProxy === "true") {
|
||||
config.behindProxy = "X-Forwarded-For";
|
||||
}
|
||||
|
||||
switch (config.behindProxy as string) {
|
||||
case "X-Forwarded-For":
|
||||
return req.headers['x-forwarded-for'] as string;
|
||||
case "Cloudflare":
|
||||
return req.headers['cf-connecting-ip'] as string;
|
||||
case "X-Real-IP":
|
||||
return req.headers['x-real-ip'] as string;
|
||||
default:
|
||||
return req.connection.remoteAddress;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
const getHash = require('./getHash.js');
|
||||
|
||||
module.exports = function getSubmissionUUID(videoID, category, userID,
|
||||
startTime, endTime) {
|
||||
return getHash('v2-categories' + videoID + startTime + endTime + category +
|
||||
userID, 1);
|
||||
};
|
||||
5
src/utils/getSubmissionUUID.ts
Normal file
5
src/utils/getSubmissionUUID.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import {getHash} from './getHash';
|
||||
|
||||
export function getSubmissionUUID(videoID: string, category: string, userID: string, startTime: number, endTime: number) {
|
||||
return getHash('v2-categories' + videoID + startTime + endTime + category + userID, 1);
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
const config = require('../config.js');
|
||||
import {config} from '../config';
|
||||
|
||||
const minimumPrefix = config.minimumPrefix || '3';
|
||||
const maximumPrefix = config.maximumPrefix || '32'; // Half the hash.
|
||||
|
||||
const prefixChecker = new RegExp('^[\\da-f]{' + minimumPrefix + ',' + maximumPrefix + '}$', 'i');
|
||||
|
||||
module.exports = (prefix) => {
|
||||
export function hashPrefixTester(prefix: string): boolean {
|
||||
return prefixChecker.test(prefix);
|
||||
};
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
var databases = require('../databases/databases.js');
|
||||
var db = databases.db;
|
||||
|
||||
//returns true if the user is considered trustworthy
|
||||
//this happens after a user has made 5 submissions and has less than 60% downvoted submissions
|
||||
|
||||
module.exports = async (userID) => {
|
||||
//check to see if this user how many submissions this user has submitted
|
||||
let totalSubmissionsRow = db.prepare('get', "SELECT count(*) as totalSubmissions, sum(votes) as voteSum FROM sponsorTimes WHERE userID = ?", [userID]);
|
||||
|
||||
if (totalSubmissionsRow.totalSubmissions > 5) {
|
||||
//check if they have a high downvote ratio
|
||||
let downvotedSubmissionsRow = db.prepare('get', "SELECT count(*) as downvotedSubmissions FROM sponsorTimes WHERE userID = ? AND (votes < 0 OR shadowHidden > 0)", [userID]);
|
||||
|
||||
return (downvotedSubmissionsRow.downvotedSubmissions / totalSubmissionsRow.totalSubmissions) < 0.6 ||
|
||||
(totalSubmissionsRow.voteSum > downvotedSubmissionsRow.downvotedSubmissions);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
20
src/utils/isUserTrustworthy.ts
Normal file
20
src/utils/isUserTrustworthy.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import {db} from '../databases/databases';
|
||||
|
||||
/**
|
||||
* Returns true if the user is considered trustworthy. This happens after a user has made 5 submissions and has less than 60% downvoted submissions
|
||||
* @param userID
|
||||
*/
|
||||
export async function isUserTrustworthy(userID: string): Promise<boolean> {
|
||||
//check to see if this user how many submissions this user has submitted
|
||||
const totalSubmissionsRow = db.prepare('get', "SELECT count(*) as totalSubmissions, sum(votes) as voteSum FROM sponsorTimes WHERE userID = ?", [userID]);
|
||||
|
||||
if (totalSubmissionsRow.totalSubmissions > 5) {
|
||||
//check if they have a high downvote ratio
|
||||
const downvotedSubmissionsRow = db.prepare('get', "SELECT count(*) as downvotedSubmissions FROM sponsorTimes WHERE userID = ? AND (votes < 0 OR shadowHidden > 0)", [userID]);
|
||||
|
||||
return (downvotedSubmissionsRow.downvotedSubmissions / totalSubmissionsRow.totalSubmissions) < 0.6 ||
|
||||
(totalSubmissionsRow.voteSum > downvotedSubmissionsRow.downvotedSubmissions);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
const databases = require('../databases/databases.js');
|
||||
const db = databases.db;
|
||||
import {db} from '../databases/databases';
|
||||
|
||||
module.exports = (userID) => {
|
||||
export function isUserVIP(userID: string): boolean {
|
||||
return db.prepare('get', "SELECT count(*) as userCount FROM vipUsers WHERE userID = ?", [userID]).userCount > 0;
|
||||
}
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
const config = require('../config.js');
|
||||
|
||||
const levels = {
|
||||
ERROR: "ERROR",
|
||||
WARN: "WARN",
|
||||
INFO: "INFO",
|
||||
DEBUG: "DEBUG"
|
||||
};
|
||||
|
||||
const colors = {
|
||||
Reset: "\x1b[0m",
|
||||
Bright: "\x1b[1m",
|
||||
Dim: "\x1b[2m",
|
||||
Underscore: "\x1b[4m",
|
||||
Blink: "\x1b[5m",
|
||||
Reverse: "\x1b[7m",
|
||||
Hidden: "\x1b[8m",
|
||||
|
||||
FgBlack: "\x1b[30m",
|
||||
FgRed: "\x1b[31m",
|
||||
FgGreen: "\x1b[32m",
|
||||
FgYellow: "\x1b[33m",
|
||||
FgBlue: "\x1b[34m",
|
||||
FgMagenta: "\x1b[35m",
|
||||
FgCyan: "\x1b[36m",
|
||||
FgWhite: "\x1b[37m",
|
||||
|
||||
BgBlack: "\x1b[40m",
|
||||
BgRed: "\x1b[41m",
|
||||
BgGreen: "\x1b[42m",
|
||||
BgYellow: "\x1b[43m",
|
||||
BgBlue: "\x1b[44m",
|
||||
BgMagenta: "\x1b[45m",
|
||||
BgCyan: "\x1b[46m",
|
||||
BgWhite: "\x1b[47m",
|
||||
}
|
||||
|
||||
const settings = {
|
||||
ERROR: true,
|
||||
WARN: true,
|
||||
INFO: false,
|
||||
DEBUG: false
|
||||
};
|
||||
|
||||
if (config.mode === 'development') {
|
||||
settings.INFO = true;
|
||||
settings.DEBUG = true;
|
||||
} else if (config.mode === 'test') {
|
||||
settings.WARN = false;
|
||||
}
|
||||
|
||||
function log(level, string) {
|
||||
if (!!settings[level]) {
|
||||
let color = colors.Bright;
|
||||
if (level === levels.ERROR) color = colors.FgRed;
|
||||
if (level === levels.WARN) color = colors.FgYellow;
|
||||
|
||||
if (level.length === 4) {level = level + " "}; // ensure logs are aligned
|
||||
console.log(colors.Dim, level + " " + new Date().toISOString() + ": ", color, string, colors.Reset);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
levels,
|
||||
log,
|
||||
error: (string) => {log(levels.ERROR, string)},
|
||||
warn: (string) => {log(levels.WARN, string)},
|
||||
info: (string) => {log(levels.INFO, string)},
|
||||
debug: (string) => {log(levels.DEBUG, string)},
|
||||
};
|
||||
89
src/utils/logger.ts
Normal file
89
src/utils/logger.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import {config} from '../config';
|
||||
|
||||
const enum LogLevel {
|
||||
ERROR = "ERROR",
|
||||
WARN = "WARN",
|
||||
INFO = "INFO",
|
||||
DEBUG = "DEBUG"
|
||||
}
|
||||
|
||||
const colors = {
|
||||
Reset: "\x1b[0m",
|
||||
Bright: "\x1b[1m",
|
||||
Dim: "\x1b[2m",
|
||||
Underscore: "\x1b[4m",
|
||||
Blink: "\x1b[5m",
|
||||
Reverse: "\x1b[7m",
|
||||
Hidden: "\x1b[8m",
|
||||
|
||||
FgBlack: "\x1b[30m",
|
||||
FgRed: "\x1b[31m",
|
||||
FgGreen: "\x1b[32m",
|
||||
FgYellow: "\x1b[33m",
|
||||
FgBlue: "\x1b[34m",
|
||||
FgMagenta: "\x1b[35m",
|
||||
FgCyan: "\x1b[36m",
|
||||
FgWhite: "\x1b[37m",
|
||||
|
||||
BgBlack: "\x1b[40m",
|
||||
BgRed: "\x1b[41m",
|
||||
BgGreen: "\x1b[42m",
|
||||
BgYellow: "\x1b[43m",
|
||||
BgBlue: "\x1b[44m",
|
||||
BgMagenta: "\x1b[45m",
|
||||
BgCyan: "\x1b[46m",
|
||||
BgWhite: "\x1b[47m",
|
||||
};
|
||||
|
||||
|
||||
class Logger {
|
||||
private _settings = {
|
||||
ERROR: true,
|
||||
WARN: true,
|
||||
INFO: false,
|
||||
DEBUG: false,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
if (config.mode === 'development') {
|
||||
this._settings.INFO = true;
|
||||
this._settings.DEBUG = true;
|
||||
} else if (config.mode === 'test') {
|
||||
this._settings.WARN = false;
|
||||
}
|
||||
}
|
||||
|
||||
error(str: string) {
|
||||
this.log(LogLevel.ERROR, str);
|
||||
}
|
||||
warn(str: string) {
|
||||
this.log(LogLevel.WARN, str);
|
||||
}
|
||||
info(str: string) {
|
||||
this.log(LogLevel.INFO, str);
|
||||
}
|
||||
debug(str: string) {
|
||||
this.log(LogLevel.DEBUG, str);
|
||||
}
|
||||
|
||||
private log(level: LogLevel, str: string) {
|
||||
if (!this._settings[level]) {
|
||||
return;
|
||||
}
|
||||
|
||||
let color = colors.Bright;
|
||||
if (level === LogLevel.ERROR) color = colors.FgRed;
|
||||
if (level === LogLevel.WARN) color = colors.FgYellow;
|
||||
|
||||
let levelStr = level.toString();
|
||||
if (levelStr.length === 4) {
|
||||
levelStr += " "; // ensure logs are aligned
|
||||
}
|
||||
console.log(colors.Dim, `${levelStr} ${new Date().toISOString()}: `, color, str, colors.Reset);
|
||||
}
|
||||
}
|
||||
|
||||
const loggerInstance = new Logger();
|
||||
export {
|
||||
loggerInstance as Logger
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
const config = require('../config.js');
|
||||
const logger = require('./logger.js');
|
||||
|
||||
if (config.redis) {
|
||||
const redis = require('redis');
|
||||
logger.info('Connected to redis');
|
||||
const client = redis.createClient(config.redis);
|
||||
module.exports = client;
|
||||
} else {
|
||||
module.exports = {
|
||||
get: (key, callback) => {
|
||||
callback(false);
|
||||
},
|
||||
set: (key, value, callback) => {
|
||||
callback(false);
|
||||
}
|
||||
};
|
||||
}
|
||||
19
src/utils/redis.ts
Normal file
19
src/utils/redis.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import {config} from '../config';
|
||||
import {Logger} from './logger';
|
||||
import redis, {Callback} from 'redis';
|
||||
|
||||
let get, set;
|
||||
if (config.redis) {
|
||||
Logger.info('Connected to redis');
|
||||
const client = redis.createClient(config.redis);
|
||||
get = client.get;
|
||||
set = client.set;
|
||||
} else {
|
||||
get = (key: string, callback?: Callback<string | null>) => callback(null, undefined);
|
||||
set = (key: string, value: string, callback?: Callback<string | null>) => callback(null, undefined);
|
||||
}
|
||||
|
||||
export {
|
||||
get,
|
||||
set,
|
||||
};
|
||||
@@ -1,52 +0,0 @@
|
||||
const config = require('../config.js');
|
||||
const logger = require('../utils/logger.js');
|
||||
const request = require('request');
|
||||
|
||||
function getVoteAuthorRaw(submissionCount, isVIP, isOwnSubmission) {
|
||||
if (isOwnSubmission) {
|
||||
return "self";
|
||||
} else if (isVIP) {
|
||||
return "vip";
|
||||
} else if (submissionCount === 0) {
|
||||
return "new";
|
||||
} else {
|
||||
return "other";
|
||||
};
|
||||
};
|
||||
|
||||
function getVoteAuthor(submissionCount, isVIP, isOwnSubmission) {
|
||||
if (submissionCount === 0) {
|
||||
return "Report by New User";
|
||||
} else if (isOwnSubmission) {
|
||||
return "Report by Submitter";
|
||||
} else if (isVIP) {
|
||||
return "Report by VIP User";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
function dispatchEvent(scope, data) {
|
||||
let webhooks = config.webhooks;
|
||||
if (webhooks === undefined || webhooks.length === 0) return;
|
||||
logger.debug("Dispatching webhooks");
|
||||
webhooks.forEach(webhook => {
|
||||
let webhookURL = webhook.url;
|
||||
let authKey = webhook.key;
|
||||
let scopes = webhook.scopes || [];
|
||||
if (!scopes.includes(scope.toLowerCase())) return;
|
||||
request.post(webhookURL, {json: data, headers: {
|
||||
"Authorization": authKey,
|
||||
"Event-Type": scope // Maybe change this in the future?
|
||||
}}).on('error', (e) => {
|
||||
logger.warn('Couldn\'t send webhook to ' + webhook.url);
|
||||
logger.warn(e);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getVoteAuthorRaw,
|
||||
getVoteAuthor,
|
||||
dispatchEvent
|
||||
}
|
||||
56
src/utils/webhookUtils.ts
Normal file
56
src/utils/webhookUtils.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import {config} from '../config';
|
||||
import {Logger} from '../utils/logger';
|
||||
import request from 'request';
|
||||
|
||||
function getVoteAuthorRaw(submissionCount: number, isVIP: boolean, isOwnSubmission: boolean): string {
|
||||
if (isOwnSubmission) {
|
||||
return "self";
|
||||
} else if (isVIP) {
|
||||
return "vip";
|
||||
} else if (submissionCount === 0) {
|
||||
return "new";
|
||||
} else {
|
||||
return "other";
|
||||
}
|
||||
}
|
||||
|
||||
function getVoteAuthor(submissionCount: number, isVIP: boolean, isOwnSubmission: boolean): string {
|
||||
if (submissionCount === 0) {
|
||||
return "Report by New User";
|
||||
} else if (isOwnSubmission) {
|
||||
return "Report by Submitter";
|
||||
} else if (isVIP) {
|
||||
return "Report by VIP User";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
function dispatchEvent(scope: string, data: any): void {
|
||||
let webhooks = config.webhooks;
|
||||
if (webhooks === undefined || webhooks.length === 0) return;
|
||||
Logger.debug("Dispatching webhooks");
|
||||
webhooks.forEach(webhook => {
|
||||
let webhookURL = webhook.url;
|
||||
let authKey = webhook.key;
|
||||
let scopes = webhook.scopes || [];
|
||||
if (!scopes.includes(scope.toLowerCase())) return;
|
||||
|
||||
// TODO TYPESCRIPT deprecated
|
||||
request.post(webhookURL, {
|
||||
json: data, headers: {
|
||||
"Authorization": authKey,
|
||||
"Event-Type": scope, // Maybe change this in the future?
|
||||
},
|
||||
}).on('error', (e) => {
|
||||
Logger.warn('Couldn\'t send webhook to ' + webhook.url);
|
||||
Logger.warn(e.message);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
getVoteAuthorRaw,
|
||||
getVoteAuthor,
|
||||
dispatchEvent,
|
||||
};
|
||||
@@ -1,62 +0,0 @@
|
||||
var config = require('../config.js');
|
||||
|
||||
// YouTube API
|
||||
const YouTubeAPI = require("youtube-api");
|
||||
const redis = require('./redis.js');
|
||||
const logger = require('./logger.js');
|
||||
|
||||
var exportObject;
|
||||
// If in test mode, return a mocked youtube object
|
||||
// otherwise return an authenticated youtube api
|
||||
if (config.mode === "test") {
|
||||
exportObject = require("../../test/youtubeMock.js");
|
||||
} else {
|
||||
YouTubeAPI.authenticate({
|
||||
type: "key",
|
||||
key: config.youtubeAPIKey
|
||||
});
|
||||
exportObject = YouTubeAPI;
|
||||
|
||||
// YouTubeAPI.videos.list wrapper with cacheing
|
||||
exportObject.listVideos = (videoID, callback) => {
|
||||
let part = 'contentDetails,snippet';
|
||||
if (videoID.length !== 11 || videoID.includes(".")) {
|
||||
callback("Invalid video ID");
|
||||
return;
|
||||
}
|
||||
|
||||
let redisKey = "youtube.video." + videoID;
|
||||
redis.get(redisKey, (getErr, result) => {
|
||||
if (getErr || !result) {
|
||||
logger.debug("redis: no cache for video information: " + videoID);
|
||||
YouTubeAPI.videos.list({
|
||||
part,
|
||||
id: videoID
|
||||
}, (ytErr, data) => {
|
||||
if (!ytErr) {
|
||||
// Only set cache if data returned
|
||||
if (data.items.length > 0) {
|
||||
redis.set(redisKey, JSON.stringify(data), (setErr) => {
|
||||
if(setErr) {
|
||||
logger.warn(setErr);
|
||||
} else {
|
||||
logger.debug("redis: video information cache set for: " + videoID);
|
||||
}
|
||||
callback(false, data); // don't fail
|
||||
});
|
||||
} else {
|
||||
callback(false, data); // don't fail
|
||||
}
|
||||
} else {
|
||||
callback(ytErr, data)
|
||||
}
|
||||
});
|
||||
} else {
|
||||
logger.debug("redis: fetched video information from cache: " + videoID);
|
||||
callback(getErr, JSON.parse(result));
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = exportObject;
|
||||
69
src/utils/youtubeApi.ts
Normal file
69
src/utils/youtubeApi.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import {config} from '../config';
|
||||
import {Logger} from './logger';
|
||||
import * as redis from './redis';
|
||||
// @ts-ignore
|
||||
import YouTubeAPI from 'youtube-api';
|
||||
|
||||
import {YouTubeAPI as youtubeApiTest} from '../../test/youtubeMock';
|
||||
|
||||
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) => {
|
||||
const part = 'contentDetails,snippet';
|
||||
if (videoID.length !== 11 || videoID.includes(".")) {
|
||||
callback("Invalid video ID", undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
const redisKey = "youtube.video." + videoID;
|
||||
redis.get(redisKey, (getErr: string, result: string) => {
|
||||
if (getErr || !result) {
|
||||
Logger.debug("redis: no cache for video information: " + videoID);
|
||||
YouTubeAPI.videos.list({
|
||||
part,
|
||||
id: videoID,
|
||||
}, (ytErr: boolean | string, data: any) => {
|
||||
if (!ytErr) {
|
||||
// Only set cache if data returned
|
||||
if (data.items.length > 0) {
|
||||
redis.set(redisKey, JSON.stringify(data), (setErr: string) => {
|
||||
if (setErr) {
|
||||
Logger.warn(setErr);
|
||||
} else {
|
||||
Logger.debug("redis: video information cache set for: " + videoID);
|
||||
}
|
||||
callback(false, data); // don't fail
|
||||
});
|
||||
} else {
|
||||
callback(false, data); // don't fail
|
||||
}
|
||||
} else {
|
||||
callback(ytErr, data);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Logger.debug("redis: fetched video information from cache: " + videoID);
|
||||
callback(getErr, JSON.parse(result));
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
_youtubeApi as YouTubeAPI
|
||||
}
|
||||
Reference in New Issue
Block a user