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
				
			
		
							
								
								
									
										109
									
								
								public/main.js
									
										
									
									
									
								
							
							
						
						
									
										109
									
								
								public/main.js
									
										
									
									
									
								
							| 
						 | 
					@ -1,4 +1,5 @@
 | 
				
			||||||
var requestsDiv = document.getElementById("requests");
 | 
					var requestsDiv = document.getElementById("requests");
 | 
				
			||||||
 | 
					var cronJobs = ['processBans']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getRequests(count,allRequests) {
 | 
					function getRequests(count,allRequests) {
 | 
				
			||||||
	var reqUrl;
 | 
						var reqUrl;
 | 
				
			||||||
| 
						 | 
					@ -65,6 +66,22 @@ function applyUrlTransforms(url) {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getColorObject() {
 | 
				
			||||||
 | 
						return {
 | 
				
			||||||
 | 
							bg: {
 | 
				
			||||||
 | 
								primary: document.getElementById('color-bg-primary').value,
 | 
				
			||||||
 | 
								table: document.getElementById('color-bg-table').value,
 | 
				
			||||||
 | 
								navbar: document.getElementById('color-bg-navbar').value,
 | 
				
			||||||
 | 
								error: document.getElementById('color-bg-error').value,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							fg: {
 | 
				
			||||||
 | 
								primary: document.getElementById('color-fg-primary').value,
 | 
				
			||||||
 | 
								ahover: document.getElementById('color-fg-ahover').value,
 | 
				
			||||||
 | 
								title: document.getElementById('color-fg-title').value,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function addRequestErr(msg) {
 | 
					function addRequestErr(msg) {
 | 
				
			||||||
	document.getElementById('addRequestError').style.display = "inline-block";
 | 
						document.getElementById('addRequestError').style.display = "inline-block";
 | 
				
			||||||
	document.getElementById('addRequestError').innerText = msg;
 | 
						document.getElementById('addRequestError').innerText = msg;
 | 
				
			||||||
| 
						 | 
					@ -85,33 +102,45 @@ function updateRequestErrReset() {
 | 
				
			||||||
	document.getElementById('updateRequestError').innerText = "";
 | 
						document.getElementById('updateRequestError').innerText = "";
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function showMessage(msg) {
 | 
					function streamerSettingsErr(msg) {
 | 
				
			||||||
	document.getElementById("messageModalText").innerText = msg;
 | 
						document.getElementById('streamerSettingsError').style.display = "inline-block";
 | 
				
			||||||
 | 
						document.getElementById('streamerSettingsError').innerText = msg;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function streamerSettingsErrReset() {
 | 
				
			||||||
 | 
						document.getElementById('streamerSettingsError').style.display = "none";
 | 
				
			||||||
 | 
						document.getElementById('streamerSettingsError').innerText = "";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Hides all modals in preparation to show a one or to close out of
 | 
				
			||||||
 | 
					// all modals and return to the page. Does NOT hide modalBackground.
 | 
				
			||||||
 | 
					function hideModals() {
 | 
				
			||||||
 | 
						document.getElementById("messageModal").style.display = "none";
 | 
				
			||||||
	document.getElementById("addRequestModal").style.display = "none";
 | 
						document.getElementById("addRequestModal").style.display = "none";
 | 
				
			||||||
	document.getElementById("updateRequestModal").style.display = "none";
 | 
						document.getElementById("updateRequestModal").style.display = "none";
 | 
				
			||||||
	document.getElementById("deleteRequestModal").style.display = "none";
 | 
						document.getElementById("deleteRequestModal").style.display = "none";
 | 
				
			||||||
 | 
						document.getElementById("streamerSettingsModal").style.display = "none";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function closeAllModals() {
 | 
				
			||||||
 | 
						hideModals();
 | 
				
			||||||
 | 
						document.getElementById("modalBackground").style.display = "none";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function showMessage(msg) {
 | 
				
			||||||
 | 
						hideModals();
 | 
				
			||||||
 | 
						document.getElementById("messageModalText").innerText = msg;
 | 
				
			||||||
	document.getElementById("modalBackground").style.display = "flex";
 | 
						document.getElementById("modalBackground").style.display = "flex";
 | 
				
			||||||
	document.getElementById("messageModal").style.display = "block";
 | 
						document.getElementById("messageModal").style.display = "block";
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function closeMessageModal() {
 | 
					 | 
				
			||||||
	document.getElementById("modalBackground").style.display = "none";
 | 
					 | 
				
			||||||
	document.getElementById("messageModal").style.display = "none";
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function openAddRequestModal() {
 | 
					function openAddRequestModal() {
 | 
				
			||||||
 | 
						hideModals();
 | 
				
			||||||
	document.getElementById("modalBackground").style.display = "flex";
 | 
						document.getElementById("modalBackground").style.display = "flex";
 | 
				
			||||||
	document.getElementById("updateRequestModal").style.display = "none";
 | 
					 | 
				
			||||||
	document.getElementById("messageModal").style.display = "none";
 | 
					 | 
				
			||||||
	document.getElementById("addRequestModal").style.display = "block";
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function closeAddRequestModal() {
 | 
					 | 
				
			||||||
	document.getElementById("modalBackground").style.display = "none";
 | 
					 | 
				
			||||||
	document.getElementById("addRequestModal").style.display = "none";
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function openUpdateRequestModal(tr) {
 | 
					function openUpdateRequestModal(tr) {
 | 
				
			||||||
 | 
						hideModals();
 | 
				
			||||||
	var url = tr.getElementsByClassName('request-link')[0].firstChild.href;
 | 
						var url = tr.getElementsByClassName('request-link')[0].firstChild.href;
 | 
				
			||||||
	var score = tr.getElementsByClassName('request-score')[0].innerText;
 | 
						var score = tr.getElementsByClassName('request-score')[0].innerText;
 | 
				
			||||||
	var state = tr.getElementsByClassName('request-state')[0].innerText;
 | 
						var state = tr.getElementsByClassName('request-state')[0].innerText;
 | 
				
			||||||
| 
						 | 
					@ -120,18 +149,12 @@ function openUpdateRequestModal(tr) {
 | 
				
			||||||
	document.getElementById("updateRequestModalCurrentScore").innerText = score;
 | 
						document.getElementById("updateRequestModalCurrentScore").innerText = score;
 | 
				
			||||||
	document.querySelector(`#updateRequestStateSelect [value="${state}"]`).selected = true;
 | 
						document.querySelector(`#updateRequestStateSelect [value="${state}"]`).selected = true;
 | 
				
			||||||
	document.getElementById("scoreModifierInput").value = 0;
 | 
						document.getElementById("scoreModifierInput").value = 0;
 | 
				
			||||||
	document.getElementById("messageModal").style.display = "none";
 | 
					 | 
				
			||||||
	document.getElementById("addRequestModal").style.display = "none";
 | 
					 | 
				
			||||||
	document.getElementById("modalBackground").style.display = "flex";
 | 
						document.getElementById("modalBackground").style.display = "flex";
 | 
				
			||||||
	document.getElementById("updateRequestModal").style.display = "block";
 | 
						document.getElementById("updateRequestModal").style.display = "block";
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function closeUpdateRequestModal() {
 | 
					 | 
				
			||||||
	document.getElementById("modalBackground").style.display = "none";
 | 
					 | 
				
			||||||
	document.getElementById("updateRequestModal").style.display = "none";
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function openDeleteRequestModal(url) {
 | 
					function openDeleteRequestModal(url) {
 | 
				
			||||||
 | 
						hideModals();
 | 
				
			||||||
	document.getElementById("updateRequestUrl").href = url;
 | 
						document.getElementById("updateRequestUrl").href = url;
 | 
				
			||||||
	document.getElementById("updateRequestUrl").innerText = url;
 | 
						document.getElementById("updateRequestUrl").innerText = url;
 | 
				
			||||||
	document.getElementById("messageModal").style.display = "none";
 | 
						document.getElementById("messageModal").style.display = "none";
 | 
				
			||||||
| 
						 | 
					@ -141,17 +164,21 @@ function openDeleteRequestModal(url) {
 | 
				
			||||||
	document.getElementById("deleteRequestModal").style.display = "block";
 | 
						document.getElementById("deleteRequestModal").style.display = "block";
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Returns to update request modal
 | 
				
			||||||
function closeDeleteRequestModal() {
 | 
					function closeDeleteRequestModal() {
 | 
				
			||||||
 | 
						hideModals();
 | 
				
			||||||
	document.getElementById("deleteRequestModal").style.display = "none";
 | 
						document.getElementById("deleteRequestModal").style.display = "none";
 | 
				
			||||||
	document.getElementById("updateRequestModal").style.display = "block";
 | 
						document.getElementById("updateRequestModal").style.display = "block";
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function closeAllModals() {
 | 
					function openStreamerSettingsModal() {
 | 
				
			||||||
	document.getElementById("messageModal").style.display = "none";
 | 
						hideModals();
 | 
				
			||||||
	document.getElementById("addRequestModal").style.display = "none";
 | 
						document.getElementById("modalBackground").style.display = "flex";
 | 
				
			||||||
	document.getElementById("updateRequestModal").style.display = "none";
 | 
						document.getElementById("streamerSettingsModal").style.display = "block";
 | 
				
			||||||
	document.getElementById("deleteRequestModal").style.display = "none";
 | 
					}
 | 
				
			||||||
	document.getElementById("modalBackground").style.display = "none";
 | 
					
 | 
				
			||||||
 | 
					function cronRequest(job) {
 | 
				
			||||||
 | 
						if (!cronJobs.includes(job)) throw new Error("Request for invalid job");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const validUrlRegexes = [
 | 
					const validUrlRegexes = [
 | 
				
			||||||
| 
						 | 
					@ -262,7 +289,33 @@ function deleteRequest(url) {
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function updatePageTitle(pageTitle) {
 | 
				
			||||||
 | 
						streamerSettingsErrReset();
 | 
				
			||||||
 | 
						fetch("/api/updatePageTitle", { method: 'POST', body: new URLSearchParams({
 | 
				
			||||||
 | 
							pageTitle: pageTitle
 | 
				
			||||||
 | 
						})})
 | 
				
			||||||
 | 
							.then(response => {
 | 
				
			||||||
 | 
								if (response.ok) {
 | 
				
			||||||
 | 
									location.reload();
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									response.text().then(streamerSettingsErr);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function updateColors(colors) {
 | 
				
			||||||
 | 
						streamerSettingsErrReset();
 | 
				
			||||||
 | 
						fetch("/api/updateColors", { method: 'POST', headers: {
 | 
				
			||||||
 | 
							'Content-Type': 'application/json'
 | 
				
			||||||
 | 
						}, body: JSON.stringify(colors)})
 | 
				
			||||||
 | 
							.then(response => {
 | 
				
			||||||
 | 
								if (response.ok) {
 | 
				
			||||||
 | 
									location.reload();
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									response.text().then(streamerSettingsErr);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
updateTable();
 | 
					updateTable();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,12 @@ button, input, select {
 | 
				
			||||||
  font-size: 100%;
 | 
					  font-size: 100%;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					input[type="color"] {
 | 
				
			||||||
 | 
					  border: none;
 | 
				
			||||||
 | 
					  padding: 0px;
 | 
				
			||||||
 | 
					  vertical-align: middle;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
a {
 | 
					a {
 | 
				
			||||||
	color: #ddd;
 | 
						color: #ddd;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										137
									
								
								src/app.ts
									
										
									
									
									
								
							
							
						
						
									
										137
									
								
								src/app.ts
									
										
									
									
									
								
							| 
						 | 
					@ -30,6 +30,7 @@ const app = express();
 | 
				
			||||||
app.use(version.checkVersionMiddleware);
 | 
					app.use(version.checkVersionMiddleware);
 | 
				
			||||||
app.use(express.static('public'));
 | 
					app.use(express.static('public'));
 | 
				
			||||||
app.use(express.urlencoded({extended: false}));
 | 
					app.use(express.urlencoded({extended: false}));
 | 
				
			||||||
 | 
					app.use(express.json());
 | 
				
			||||||
app.use(session({
 | 
					app.use(session({
 | 
				
			||||||
	secret: config.sessionSecret,
 | 
						secret: config.sessionSecret,
 | 
				
			||||||
	saveUninitialized: false,
 | 
						saveUninitialized: false,
 | 
				
			||||||
| 
						 | 
					@ -189,6 +190,101 @@ app.post("/api/updateRequestScoreModifier", async (request, response) => {
 | 
				
			||||||
	.catch((e: any) => errorHandler(request,response,e));
 | 
						.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) => {
 | 
					app.post("/api/deleteRequest", async (request, response) => {
 | 
				
			||||||
	if (request.session) await validateApiToken(request.session);
 | 
						if (request.session) await validateApiToken(request.session);
 | 
				
			||||||
	if (!request.session || !request.session.user) {
 | 
						if (!request.session || !request.session.user) {
 | 
				
			||||||
| 
						 | 
					@ -266,6 +362,37 @@ app.post("/api/deleteVote", async (request,response) => {
 | 
				
			||||||
	.catch((e: any) => errorHandler(request,response,e));
 | 
						.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
 | 
					// Twitch callback
 | 
				
			||||||
app.get("/callback", async (request, response) => {
 | 
					app.get("/callback", async (request, response) => {
 | 
				
			||||||
	if (request.query.error) {
 | 
						if (request.query.error) {
 | 
				
			||||||
| 
						 | 
					@ -302,7 +429,7 @@ app.get("/callback", async (request, response) => {
 | 
				
			||||||
app.get("/", async (request, response) => {
 | 
					app.get("/", async (request, response) => {
 | 
				
			||||||
	if (request.session) await validateApiToken(request.session);
 | 
						if (request.session) await validateApiToken(request.session);
 | 
				
			||||||
	var streamerInfo = await db.query(queries.getStreamerInfo).then((result: pg.QueryResult) => result.rows[0]);
 | 
						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') {
 | 
						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`);
 | 
							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;
 | 
							return;
 | 
				
			||||||
| 
						 | 
					@ -312,7 +439,7 @@ app.get("/", async (request, response) => {
 | 
				
			||||||
			loggedIn: false,
 | 
								loggedIn: false,
 | 
				
			||||||
			clientId: config.twitchClientId,
 | 
								clientId: config.twitchClientId,
 | 
				
			||||||
			urlPrefix: config.urlPrefix,
 | 
								urlPrefix: config.urlPrefix,
 | 
				
			||||||
			pageTitle: config.title,
 | 
								pageTitle: streamerConfig.title,
 | 
				
			||||||
			streamerName: streamerInfo['displayname'],
 | 
								streamerName: streamerInfo['displayname'],
 | 
				
			||||||
			streamerProfilePicture: streamerInfo['imageurl']
 | 
								streamerProfilePicture: streamerInfo['imageurl']
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
| 
						 | 
					@ -324,9 +451,10 @@ app.get("/", async (request, response) => {
 | 
				
			||||||
			userProfilePicture: request.session.user.profile_image_url,
 | 
								userProfilePicture: request.session.user.profile_image_url,
 | 
				
			||||||
			validStates: validStates,
 | 
								validStates: validStates,
 | 
				
			||||||
			isStreamer: streamerInfo['userid'] == request.session.user.id,
 | 
								isStreamer: streamerInfo['userid'] == request.session.user.id,
 | 
				
			||||||
			pageTitle: config.title,
 | 
								pageTitle: streamerConfig.title,
 | 
				
			||||||
			streamerName: streamerInfo['displayname'],
 | 
								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) => {
 | 
					app.get("/colors.css", async (_request, response) => {
 | 
				
			||||||
	var streamerInfo = await db.query(queries.getStreamerInfo).then((result: pg.QueryResult) => result.rows[0]);
 | 
						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']);
 | 
						var colors = await db.query(queries.getConfig).then((result: pg.QueryResult) => result.rows[0]['colors']);
 | 
				
			||||||
	console.log(colors);
 | 
					 | 
				
			||||||
	if (typeof streamerInfo == 'undefined') return;
 | 
						if (typeof streamerInfo == 'undefined') return;
 | 
				
			||||||
	response.contentType("text/css");
 | 
						response.contentType("text/css");
 | 
				
			||||||
	response.render('colors.eta', colors);
 | 
						response.render('colors.eta', colors);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										22
									
								
								src/cron.ts
									
										
									
									
									
								
							
							
						
						
									
										22
									
								
								src/cron.ts
									
										
									
									
									
								
							| 
						 | 
					@ -9,6 +9,24 @@ interface CronJob {
 | 
				
			||||||
	(streamer: twitch.StreamerUserIdTokenPair): Promise<void>
 | 
						(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() {
 | 
					async function run() {
 | 
				
			||||||
	// If instance is not yet set up, end processing at this point
 | 
						// If instance is not yet set up, end processing at this point
 | 
				
			||||||
	var streamer = await db.query(queries.getStreamerIdToken).then(
 | 
						var streamer = await db.query(queries.getStreamerIdToken).then(
 | 
				
			||||||
| 
						 | 
					@ -42,11 +60,11 @@ async function run() {
 | 
				
			||||||
			log(LogLevel.ERROR,`cron: Job ${job} exception message: ${e}`)
 | 
								log(LogLevel.ERROR,`cron: Job ${job} exception message: ${e}`)
 | 
				
			||||||
		} finally {
 | 
							} finally {
 | 
				
			||||||
			log(LogLevel.DEBUG,`cron: Job ${job} hit finally; releasing dbconn`);
 | 
								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,"cron: Finished job " + job);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	log(LogLevel.INFO,"End cron run")
 | 
						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"
 | 
						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
 | 
					// Request-related queries
 | 
				
			||||||
export const getRequests = {
 | 
					export const getRequests = {
 | 
				
			||||||
	name: "getRequests",
 | 
						name: "getRequests",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@
 | 
				
			||||||
	<head>
 | 
						<head>
 | 
				
			||||||
		<link rel=stylesheet href=/style.css />
 | 
							<link rel=stylesheet href=/style.css />
 | 
				
			||||||
		<link rel=stylesheet href=/colors.css />
 | 
							<link rel=stylesheet href=/colors.css />
 | 
				
			||||||
		<title><%= it.pageTitle.replace('{username}',it.streamerName) %></title>
 | 
							<title><%~ it.pageTitle.replace('{username}',it.streamerName) %></title>
 | 
				
			||||||
		<script>
 | 
							<script>
 | 
				
			||||||
			window.loggedIn = <%= it.loggedIn %>;
 | 
								window.loggedIn = <%= it.loggedIn %>;
 | 
				
			||||||
			window.validStates = <%~ it.validStates %>;
 | 
								window.validStates = <%~ it.validStates %>;
 | 
				
			||||||
| 
						 | 
					@ -18,6 +18,9 @@
 | 
				
			||||||
			<div id="nav-requests"><a href="/">Requests</a></div>
 | 
								<div id="nav-requests"><a href="/">Requests</a></div>
 | 
				
			||||||
			<%- if (it.loggedIn) { -%>
 | 
								<%- if (it.loggedIn) { -%>
 | 
				
			||||||
			<div id="nav-addrequest"><a href="#" onclick="openAddRequestModal()">Add Request</a></div>
 | 
								<div id="nav-addrequest"><a href="#" onclick="openAddRequestModal()">Add Request</a></div>
 | 
				
			||||||
 | 
								<%- if (it.isStreamer) { -%>
 | 
				
			||||||
 | 
								<div id="nav-streamersettings"><a href="#" onclick="openStreamerSettingsModal()">Streamer Settings</a></div>
 | 
				
			||||||
 | 
								<%- } %>
 | 
				
			||||||
			<div id="nav-userpic"><img src="<%= it.userProfilePicture %>" /></div>
 | 
								<div id="nav-userpic"><img src="<%= it.userProfilePicture %>" /></div>
 | 
				
			||||||
			<div id="nav-username"><%= it.userName %></div>
 | 
								<div id="nav-username"><%= it.userName %></div>
 | 
				
			||||||
			<div id="nav-logout"><a href="/logout">Logout</a></div>
 | 
								<div id="nav-logout"><a href="/logout">Logout</a></div>
 | 
				
			||||||
| 
						 | 
					@ -39,11 +42,11 @@
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	<div id="modalBackground">
 | 
						<div id="modalBackground">
 | 
				
			||||||
			<div class="modal" id="messageModal">
 | 
								<div class="modal" id="messageModal">
 | 
				
			||||||
				<div class="modalClose"><a href="#" onclick="closeMessageModal()">×</a></div>
 | 
									<div class="modalClose"><a href="#" onclick="closeAllModals()">×</a></div>
 | 
				
			||||||
				<span id="messageModalText"></span>
 | 
									<span id="messageModalText"></span>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
			<div class="modal" id="addRequestModal">
 | 
								<div class="modal" id="addRequestModal">
 | 
				
			||||||
				<div class="modalClose"><a href="#" onclick="closeAddRequestModal()">×</a></div>
 | 
									<div class="modalClose"><a href="#" onclick="closeAllModals()">×</a></div>
 | 
				
			||||||
				<h1>Add Request</h1>
 | 
									<h1>Add Request</h1>
 | 
				
			||||||
				<div class="error" id="addRequestError"></div>
 | 
									<div class="error" id="addRequestError"></div>
 | 
				
			||||||
				<span id="addRequestInputContainer">
 | 
									<span id="addRequestInputContainer">
 | 
				
			||||||
| 
						 | 
					@ -53,7 +56,7 @@
 | 
				
			||||||
				Currently, only Youtube links are accepted.
 | 
									Currently, only Youtube links are accepted.
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
			<div class="modal" id="updateRequestModal">
 | 
								<div class="modal" id="updateRequestModal">
 | 
				
			||||||
				<div class="modalClose"><a href="#" onclick="closeAddRequestModal()">×</a></div>
 | 
									<div class="modalClose"><a href="#" onclick="closeAllModals()">×</a></div>
 | 
				
			||||||
				<h2>Update Request</h2>
 | 
									<h2>Update Request</h2>
 | 
				
			||||||
				<div class="error" id="updateRequestError"></div>
 | 
									<div class="error" id="updateRequestError"></div>
 | 
				
			||||||
				<br>
 | 
									<br>
 | 
				
			||||||
| 
						 | 
					@ -84,7 +87,7 @@
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
				<br>
 | 
									<br>
 | 
				
			||||||
				<div>
 | 
									<div>
 | 
				
			||||||
					<a id="updateMetadataLink" href="#" onclick="updateRequestMetadata(
 | 
										<a href="#" onclick="updateRequestMetadata(
 | 
				
			||||||
						document.getElementById('updateRequestUrl').innerText
 | 
											document.getElementById('updateRequestUrl').innerText
 | 
				
			||||||
					)">Update Request Metadata</a>
 | 
										)">Update Request Metadata</a>
 | 
				
			||||||
					<br>
 | 
										<br>
 | 
				
			||||||
| 
						 | 
					@ -112,6 +115,43 @@
 | 
				
			||||||
				<button onclick="closeDeleteRequestModal()">No</button>
 | 
									<button onclick="closeDeleteRequestModal()">No</button>
 | 
				
			||||||
				<button onclick="deleteRequest(document.getElementById('updateRequestUrl').innerText)">Yes</button>
 | 
									<button onclick="deleteRequest(document.getElementById('updateRequestUrl').innerText)">Yes</button>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
 | 
								<div class="modal" id="streamerSettingsModal">
 | 
				
			||||||
 | 
									<div class="modalClose"><a href="#" onclick="closeAllModals()">×</a></div>
 | 
				
			||||||
 | 
									<h2>Streamer Settings</h2>
 | 
				
			||||||
 | 
									<div class="error" id="streamerSettingsError"></div>
 | 
				
			||||||
 | 
									<br>
 | 
				
			||||||
 | 
									<div>
 | 
				
			||||||
 | 
										<h3 style='margin-bottom: 0.5em'>Customization</h3>
 | 
				
			||||||
 | 
										<p>
 | 
				
			||||||
 | 
											Page Title:
 | 
				
			||||||
 | 
											<input type="text" id="pageTitle" style="width: 15em" value="<%~ it.pageTitle %>"></input>
 | 
				
			||||||
 | 
											<button onclick="updatePageTitle(document.getElementById('pageTitle').value)">Submit</button>
 | 
				
			||||||
 | 
											<button onclick="updatePageTitle('{username}\'s Learn Request Queue')">Reset</button>
 | 
				
			||||||
 | 
										</p>
 | 
				
			||||||
 | 
										<p>
 | 
				
			||||||
 | 
											<h3>Colors:</h3>
 | 
				
			||||||
 | 
											<p>Click a color to change it</p>
 | 
				
			||||||
 | 
											<b>Background:</b><br>
 | 
				
			||||||
 | 
											Primary: <input type="color" id="color-bg-primary" value="<%= it.colors.bg.primary %>"></input><br>
 | 
				
			||||||
 | 
											Table: <input type="color" id="color-bg-table" value="<%= it.colors.bg.table %>"></input><br>
 | 
				
			||||||
 | 
											Navbar: <input type="color" id="color-bg-navbar" value="<%= it.colors.bg.navbar %>"></input><br>
 | 
				
			||||||
 | 
											Error: <input type="color" id="color-bg-error" value="<%= it.colors.bg.error %>"></input><br>
 | 
				
			||||||
 | 
											<br>
 | 
				
			||||||
 | 
											<b>Foreground:</b><br>
 | 
				
			||||||
 | 
											Primary: <input type="color" id="color-fg-primary" value="<%= it.colors.fg.primary %>"></input><br>
 | 
				
			||||||
 | 
											Link Hover: <input type="color" id="color-fg-ahover" value="<%= it.colors.fg.ahover %>"></input><br>
 | 
				
			||||||
 | 
											Title: <input type="color" id="color-fg-title" value="<%= it.colors.fg.title %>"></input><br>
 | 
				
			||||||
 | 
											<br>
 | 
				
			||||||
 | 
											<button onclick="updateColors(getColorObject())">Submit</button>
 | 
				
			||||||
 | 
											<button onclick="updateColors({bg:{error: '#ff0000',table: '#282828',navbar: '#666666',primary: '#444444'},fg: { title: '#eeeeee', ahover: '#ffffff', primary: '#dddddd'}})">Reset</button>
 | 
				
			||||||
 | 
										</p>
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
 | 
									<hr>
 | 
				
			||||||
 | 
									<div>
 | 
				
			||||||
 | 
										<h3 style='margin-bottom: 0.5em'>Batch Jobs</h3>
 | 
				
			||||||
 | 
										<a href="#" onclick="cronRequest('processBans')">Force Refresh Banned Users (NYI)</a>
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</body>
 | 
						</body>
 | 
				
			||||||
</html>
 | 
					</html>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue