Customization!
Add ability to control page title and color scheme. Also, partially implements manual triggering of cronjobs. Fixes #18
This commit is contained in:
parent
1c34b3f013
commit
6495e1c8ef
6 changed files with 294 additions and 40 deletions
137
src/app.ts
137
src/app.ts
|
@ -30,6 +30,7 @@ const app = express();
|
|||
app.use(version.checkVersionMiddleware);
|
||||
app.use(express.static('public'));
|
||||
app.use(express.urlencoded({extended: false}));
|
||||
app.use(express.json());
|
||||
app.use(session({
|
||||
secret: config.sessionSecret,
|
||||
saveUninitialized: false,
|
||||
|
@ -189,6 +190,101 @@ app.post("/api/updateRequestScoreModifier", async (request, response) => {
|
|||
.catch((e: any) => errorHandler(request,response,e));
|
||||
});
|
||||
|
||||
app.post("/api/updatePageTitle", async (request, response) => {
|
||||
if (request.session) await validateApiToken(request.session);
|
||||
if (!request.session || !request.session.user) {
|
||||
response.status(401);
|
||||
response.send("Session expired; please log in again");
|
||||
return;
|
||||
}
|
||||
var streamerid = await db.query(queries.getStreamerId).then((result: pg.QueryResult) => result.rows[0]['userid']);
|
||||
if (request.session.user.id != streamerid) {
|
||||
response.status(401);
|
||||
response.send("You are not the streamer");
|
||||
return;
|
||||
}
|
||||
if (!request.body.pageTitle) {
|
||||
response.status(400);
|
||||
response.send("Missing pageTitle");
|
||||
return;
|
||||
}
|
||||
var pageTitle = request.body.pageTitle as string;
|
||||
response.type('text/plain');
|
||||
pageTitle = pageTitle.replace(/[&<>"']/g, function(m) {
|
||||
switch (m) {
|
||||
case '&':
|
||||
return '&';
|
||||
case '<':
|
||||
return '<';
|
||||
case '>':
|
||||
return '>';
|
||||
case '"':
|
||||
return '"';
|
||||
case "'":
|
||||
return ''';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
});
|
||||
await db.query(Object.assign(queries.updatePageTitle,{ values: [pageTitle] }))
|
||||
.catch((e: any) => errorHandler(request,response,e));
|
||||
response.status(200);
|
||||
response.send('Successfully updated page title');
|
||||
});
|
||||
|
||||
app.post("/api/updateColors", async (request, response) => {
|
||||
if (request.session) await validateApiToken(request.session);
|
||||
if (!request.session || !request.session.user) {
|
||||
response.status(401);
|
||||
response.send("Session expired; please log in again");
|
||||
return;
|
||||
}
|
||||
var streamerid = await db.query(queries.getStreamerId).then((result: pg.QueryResult) => result.rows[0]['userid']);
|
||||
if (request.session.user.id != streamerid) {
|
||||
response.status(401);
|
||||
response.send("You are not the streamer");
|
||||
return;
|
||||
}
|
||||
if (!request.body.bg) {
|
||||
response.status(400);
|
||||
response.send("Missing bg");
|
||||
return;
|
||||
}
|
||||
if (!request.body.fg) {
|
||||
response.status(400);
|
||||
response.send("Missing fg");
|
||||
return;
|
||||
}
|
||||
type Colors = { [key: string]: { [key: string]: string} }
|
||||
var colors: Colors = { bg: {}, fg: {} };
|
||||
console.log(JSON.stringify(request.body,null,2));
|
||||
for (var color of ['primary','table','navbar','error']) {
|
||||
var setcolor = request.body.bg[color];
|
||||
if (/^#[0-9a-fA-F]{6}$/.test(setcolor)) {
|
||||
colors.bg[color] = setcolor
|
||||
} else {
|
||||
response.status(400);
|
||||
response.send(`Color 'bg.${color}' missing or invalid`)
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (var color of ['primary','ahover','title']) {
|
||||
var setcolor = request.body.fg[color];
|
||||
if (/^#[0-9a-fA-F]{6}$/.test(setcolor)) {
|
||||
colors.fg[color] = setcolor
|
||||
} else {
|
||||
response.status(400);
|
||||
response.send(`Color 'fg.${color}' missing or invalid`)
|
||||
return;
|
||||
}
|
||||
}
|
||||
response.type('text/plain');
|
||||
await db.query(Object.assign(queries.updateColors,{ values: [JSON.stringify(colors)] }))
|
||||
.catch((e: any) => errorHandler(request,response,e));
|
||||
response.status(200);
|
||||
response.send('Successfully updated colors');
|
||||
});
|
||||
|
||||
app.post("/api/deleteRequest", async (request, response) => {
|
||||
if (request.session) await validateApiToken(request.session);
|
||||
if (!request.session || !request.session.user) {
|
||||
|
@ -266,6 +362,37 @@ app.post("/api/deleteVote", async (request,response) => {
|
|||
.catch((e: any) => errorHandler(request,response,e));
|
||||
});
|
||||
|
||||
app.get("/api/cronRequest", async (request, response) => {
|
||||
if (request.session) await validateApiToken(request.session);
|
||||
if (!request.session || !request.session.user) {
|
||||
response.status(401);
|
||||
response.send("Session expired; please log in again");
|
||||
return;
|
||||
}
|
||||
var streamerid = await db.query(queries.getStreamerId).then((result: pg.QueryResult) => result.rows[0]['userid']);
|
||||
if (request.session.user.id != streamerid) {
|
||||
response.status(401);
|
||||
response.send("You are not the streamer");
|
||||
return;
|
||||
}
|
||||
if (!request.query.job) {
|
||||
response.status(400);
|
||||
response.send("Missing job");
|
||||
return;
|
||||
}
|
||||
var job = request.body.job as string;
|
||||
try {
|
||||
cron.validateJob(job)
|
||||
} catch (e) {
|
||||
response.status(400);
|
||||
response.send("Invalid job")
|
||||
return;
|
||||
}
|
||||
response.type('text/plain');
|
||||
cron.request(job).catch((e: any) => errorHandler(request,response,e));
|
||||
});
|
||||
|
||||
|
||||
// Twitch callback
|
||||
app.get("/callback", async (request, response) => {
|
||||
if (request.query.error) {
|
||||
|
@ -302,7 +429,7 @@ app.get("/callback", async (request, response) => {
|
|||
app.get("/", async (request, response) => {
|
||||
if (request.session) await validateApiToken(request.session);
|
||||
var streamerInfo = await db.query(queries.getStreamerInfo).then((result: pg.QueryResult) => result.rows[0]);
|
||||
var config = await db.query(queries.getConfig).then((result: pg.QueryResult) => result.rows[0]);
|
||||
var streamerConfig = await db.query(queries.getConfig).then((result: pg.QueryResult) => result.rows[0]);
|
||||
if (typeof streamerInfo == 'undefined') {
|
||||
response.redirect(307, `https://id.twitch.tv/oauth2/authorize?client_id=${config.twitchClientId}&redirect_uri=${config.urlPrefix}/callback&response_type=code&scope=channel:read:subscriptions moderation:read`);
|
||||
return;
|
||||
|
@ -312,7 +439,7 @@ app.get("/", async (request, response) => {
|
|||
loggedIn: false,
|
||||
clientId: config.twitchClientId,
|
||||
urlPrefix: config.urlPrefix,
|
||||
pageTitle: config.title,
|
||||
pageTitle: streamerConfig.title,
|
||||
streamerName: streamerInfo['displayname'],
|
||||
streamerProfilePicture: streamerInfo['imageurl']
|
||||
});
|
||||
|
@ -324,9 +451,10 @@ app.get("/", async (request, response) => {
|
|||
userProfilePicture: request.session.user.profile_image_url,
|
||||
validStates: validStates,
|
||||
isStreamer: streamerInfo['userid'] == request.session.user.id,
|
||||
pageTitle: config.title,
|
||||
pageTitle: streamerConfig.title,
|
||||
streamerName: streamerInfo['displayname'],
|
||||
streamerProfilePicture: streamerInfo['imageurl']
|
||||
streamerProfilePicture: streamerInfo['imageurl'],
|
||||
colors: streamerConfig.colors
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -334,7 +462,6 @@ app.get("/", async (request, response) => {
|
|||
app.get("/colors.css", async (_request, response) => {
|
||||
var streamerInfo = await db.query(queries.getStreamerInfo).then((result: pg.QueryResult) => result.rows[0]);
|
||||
var colors = await db.query(queries.getConfig).then((result: pg.QueryResult) => result.rows[0]['colors']);
|
||||
console.log(colors);
|
||||
if (typeof streamerInfo == 'undefined') return;
|
||||
response.contentType("text/css");
|
||||
response.render('colors.eta', colors);
|
||||
|
|
22
src/cron.ts
22
src/cron.ts
|
@ -9,6 +9,24 @@ interface CronJob {
|
|||
(streamer: twitch.StreamerUserIdTokenPair): Promise<void>
|
||||
}
|
||||
|
||||
function validateJob(job: string) {
|
||||
if (!Object.keys(cronjobs).includes(job)) throw new Error("Invalid cronjob " + job);
|
||||
}
|
||||
|
||||
async function runJob(job: string) {
|
||||
validateJob(job);
|
||||
var streamer = await db.query(queries.getStreamerIdToken).then(
|
||||
(result: pg.QueryResult) => result.rows[0]);
|
||||
return await (cronjobs as { [key: string]: CronJob })[job](streamer);
|
||||
}
|
||||
|
||||
// Run a specific job on request of streamer
|
||||
async function request(job: string) {
|
||||
validateJob(job);
|
||||
// TODO: Rate limiting
|
||||
await runJob(job);
|
||||
}
|
||||
|
||||
async function run() {
|
||||
// If instance is not yet set up, end processing at this point
|
||||
var streamer = await db.query(queries.getStreamerIdToken).then(
|
||||
|
@ -42,11 +60,11 @@ async function run() {
|
|||
log(LogLevel.ERROR,`cron: Job ${job} exception message: ${e}`)
|
||||
} finally {
|
||||
log(LogLevel.DEBUG,`cron: Job ${job} hit finally; releasing dbconn`);
|
||||
await dbconn.release();
|
||||
dbconn.release();
|
||||
}
|
||||
log(LogLevel.INFO,"cron: Finished job " + job);
|
||||
}
|
||||
log(LogLevel.INFO,"End cron run")
|
||||
}
|
||||
|
||||
export = {run}
|
||||
export = {run,validateJob,request}
|
||||
|
|
|
@ -37,6 +37,16 @@ export const updateStreamer = {
|
|||
ON CONFLICT (userid) DO UPDATE SET tokenPair = $2"
|
||||
}
|
||||
|
||||
export const updatePageTitle = {
|
||||
name: "updatePageTitle",
|
||||
text: "UPDATE config SET title = $1"
|
||||
}
|
||||
|
||||
export const updateColors = {
|
||||
name: "updateColors",
|
||||
text: "UPDATE config SET colors = $1"
|
||||
}
|
||||
|
||||
// Request-related queries
|
||||
export const getRequests = {
|
||||
name: "getRequests",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue