514 lines
17 KiB
JavaScript
514 lines
17 KiB
JavaScript
/*Canopy - The next generation of stoner streaming software
|
|
Copyright (C) 2024-2025 Rainbownapkin and the TTN Community
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as
|
|
published by the Free Software Foundation, either version 3 of the
|
|
License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
|
|
|
class adminUserList{
|
|
constructor(){
|
|
this.userNames = document.querySelectorAll(".admin-user-list-name");
|
|
this.rankSelectors = document.querySelectorAll(".admin-user-list-rank-select");
|
|
this.banIcons = document.querySelectorAll(".admin-user-list-ban-icon");
|
|
this.passResetIcons = document.querySelectorAll(".admin-user-list-pw-reset-icon");
|
|
|
|
this.setupInput();
|
|
}
|
|
|
|
setupInput(){
|
|
for(let userName of this.userNames){
|
|
//Splice username out of class name
|
|
const name = userName.id.replace('admin-user-list-name-','');
|
|
|
|
userName.addEventListener('mouseenter',(event)=>{utils.ux.displayTooltip(event, `altList?user=${name}`, true);});
|
|
}
|
|
|
|
for(let rankSelector of this.rankSelectors){
|
|
rankSelector.addEventListener("change", this.setRank.bind(this))
|
|
}
|
|
|
|
for(let banIcon of this.banIcons){
|
|
banIcon.addEventListener("click", this.banPopup.bind(this));
|
|
}
|
|
|
|
for(let passResetIcon of this.passResetIcons){
|
|
passResetIcon.addEventListener("click", this.genResetLink.bind(this))
|
|
}
|
|
}
|
|
|
|
async setRank(event){
|
|
const user = event.target.id.replace("admin-user-list-rank-select-","");
|
|
const rank = event.target.value;
|
|
|
|
this.updateSelect(await adminUtil.setUserRank(user, rank), event.target);
|
|
}
|
|
|
|
async genResetLink(event){
|
|
//Scrape user
|
|
const user = event.target.id.replace("admin-user-list-pw-reset-icon-","");
|
|
|
|
const URL = (await adminUtil.genPasswordResetLink(user)).url;
|
|
|
|
//Create span
|
|
const span = document.createElement('span');
|
|
//Usually not into doing CSS this way, but I'm not making a dedicated file for a popup this small...
|
|
span.style = "text-align: center; display: block;"
|
|
|
|
//Create header
|
|
const header = document.createElement('h3');
|
|
header.innerText = `Reset Link for ${user}`
|
|
|
|
//Create link
|
|
const link = document.createElement('a');
|
|
link.innerText = "Reset Link"
|
|
link.href = URL;
|
|
|
|
//Append link to the header
|
|
span.appendChild(header);
|
|
span.appendChild(link);
|
|
|
|
//Display link in pop-up
|
|
new canopyUXUtils.popup(span.outerHTML);
|
|
|
|
}
|
|
|
|
banPopup(event){
|
|
const user = event.target.id.replace("admin-user-list-ban-icon-","");
|
|
new banUserPopup(user, userBanList.renderBanList.bind(userBanList));
|
|
}
|
|
|
|
updateSelect(update, select){
|
|
if(update != null){
|
|
select.value = update.rank;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
class adminPermissionList{
|
|
constructor(){
|
|
this.permissionSelectors = document.querySelectorAll(".admin-perm-list-rank-select");
|
|
this.channelPermissionSelectors = document.querySelectorAll(".admin-chan-perm-list-rank-select");
|
|
|
|
this.setupInput();
|
|
}
|
|
|
|
setupInput(){
|
|
this.permissionSelectors.forEach((permissionSelector)=>{
|
|
permissionSelector.addEventListener("change", this.setPerm.bind(this))
|
|
});
|
|
|
|
this.channelPermissionSelectors.forEach((permissionSelector)=>{
|
|
permissionSelector.addEventListener("change", this.setChanPerm.bind(this))
|
|
});
|
|
}
|
|
|
|
async setPerm(event){
|
|
const permMap = new Map([[event.target.id.replace("admin-perm-list-rank-select-",""), event.target.value]]);
|
|
|
|
this.updateSelect(await adminUtil.setPermission(permMap), event.target);
|
|
}
|
|
|
|
async setChanPerm(event){
|
|
const permMap = new Map([[event.target.id.replace("admin-chan-perm-list-rank-select-",""), event.target.value]]);
|
|
|
|
this.updateChanSelect(await adminUtil.setChannelOverride(permMap), event.target);
|
|
}
|
|
|
|
updateSelect(update, select){
|
|
if(update != null){
|
|
var perm = select.id.replace("admin-perm-list-rank-select-","");
|
|
|
|
select.value = update[perm];
|
|
}
|
|
}
|
|
|
|
updateChanSelect(update, select){
|
|
if(update != null){
|
|
var perm = select.id.replace("admin-chan-perm-list-rank-select-","");
|
|
|
|
select.value = update.channelOverrides[perm];
|
|
}
|
|
}
|
|
}
|
|
|
|
class adminUserBanList{
|
|
constructor(){
|
|
this.table = document.querySelector("#admin-ban-list-table");
|
|
|
|
this.getBanList();
|
|
}
|
|
|
|
async getBanList(){
|
|
this.renderBanList(await adminUtil.getBans());
|
|
}
|
|
|
|
clearBanList(){
|
|
const oldRows = this.table.querySelectorAll('tr.gen-row');
|
|
oldRows.forEach((row) => {
|
|
row.remove();
|
|
});
|
|
}
|
|
|
|
async unban(event){
|
|
//Get username from target id
|
|
const user = event.target.id.replace("admin-user-list-unban-icon-","");
|
|
//Send unban command to server and display the resulting banlist
|
|
this.renderBanList(await adminUtil.unbanUser(user));
|
|
}
|
|
|
|
renderBanList(banList){
|
|
//Clear out the ban list
|
|
this.clearBanList();
|
|
|
|
//For each ban received
|
|
banList.forEach((ban) => {
|
|
//Calculate expiration date and expiration days
|
|
let expirationDateString = `${new Date(ban.expirationDate).toLocaleDateString()}<br>(${ban.daysUntilExpiration} day(s) left)`;
|
|
let banActionString = ban.permanent ? "Nuke<br>Accounts" : "Un-Ban";
|
|
let nuked = ban.user == null;
|
|
|
|
//If the user is null (the ban has been nuked)
|
|
if(nuked){
|
|
//Fudge the user object if it's already been deleted
|
|
ban.user = {
|
|
//Use dead name if we got one
|
|
user: ban.deletedNames[0] != null ? ban.deletedNames[0] : "Nuked"
|
|
};
|
|
|
|
ban.alts[0] = {
|
|
user: "Nuked"
|
|
}
|
|
|
|
//Fake the display string
|
|
expirationDateString = "Accounts<br>Nuked"
|
|
banActionString = "Accounts<br>Nuked"
|
|
}
|
|
|
|
//Generate and append row to table
|
|
this.table.appendChild(utils.ux.newTableRow([
|
|
this.renderUser(ban.user, nuked),
|
|
this.renderUsers(ban.alts, nuked),
|
|
this.renderDeadUsers(ban.deletedNames),
|
|
this.renderIPs(ban.ips),
|
|
new Date(ban.banDate).toLocaleDateString(),
|
|
expirationDateString,
|
|
banActionString,
|
|
this.renderIcons(ban.user)
|
|
]));
|
|
});
|
|
}
|
|
|
|
renderUser(user, nuked = false){
|
|
if(nuked){
|
|
var userNode = document.createElement('img');
|
|
userNode.classList.add("admin-list-entry","admin-list-entry-item");
|
|
userNode.src = '/img/nuked.png'
|
|
userNode.title = "Nuked"
|
|
}else{
|
|
var userNode = document.createElement('p');
|
|
userNode.innerHTML = user.user;
|
|
}
|
|
return userNode;
|
|
}
|
|
|
|
renderUsers(users, nuked){
|
|
//Create userlist span
|
|
let userList = document.createElement('span');
|
|
|
|
if(!nuked){
|
|
//For each user
|
|
for(let user of users){
|
|
//Render out the user
|
|
userList.appendChild(this.renderUser(user));
|
|
}
|
|
}else{
|
|
userList = document.createElement('img');
|
|
userList.classList.add("admin-list-entry","admin-list-entry-item");
|
|
userList.src = '/img/nuked.png'
|
|
userList.title = "Nuked"
|
|
}
|
|
|
|
//return our list
|
|
return userList;
|
|
}
|
|
|
|
renderDeadUsers(users){
|
|
//Create userlist span
|
|
const deadUsers = document.createElement('span');
|
|
|
|
//For each ip
|
|
for(let user of users){
|
|
//Create a node
|
|
const userNode = document.createElement('p');
|
|
//Fill it wit the ip
|
|
userNode.innerHTML = user;
|
|
//Append it
|
|
deadUsers.appendChild(userNode);
|
|
}
|
|
|
|
//return our list
|
|
return deadUsers;
|
|
}
|
|
|
|
renderIPs(ips){
|
|
//Create userlist span
|
|
const ipList = document.createElement('span');
|
|
|
|
//For each ip
|
|
for(let ip of ips.plaintext){
|
|
//Create a node
|
|
const ipNode = document.createElement('p');
|
|
//Fill it wit the ip
|
|
ipNode.innerHTML = ip;
|
|
//Append it
|
|
ipList.appendChild(ipNode);
|
|
}
|
|
|
|
//For each user
|
|
for(let hash of ips.hashed){
|
|
//Create a node
|
|
const ipNode = document.createElement('p');
|
|
//List it as a hashed ip with the hash as alt text
|
|
ipNode.innerHTML = '[Hashed IP]';
|
|
ipNode.title = hash;
|
|
//Append the node
|
|
ipList.appendChild(ipNode);
|
|
}
|
|
|
|
//return our list
|
|
return ipList;
|
|
}
|
|
|
|
renderIcons(user){
|
|
//Create unban icon
|
|
const unbanIcon = document.createElement('i');
|
|
unbanIcon.classList.add("bi-emoji-smile-fill","admin-user-list-icon","admin-user-list-unban-icon");
|
|
unbanIcon.id = `admin-user-list-unban-icon-${user.user}`;
|
|
unbanIcon.title = `Unban ${user.user}`;
|
|
unbanIcon.addEventListener("click", this.unban.bind(this));
|
|
|
|
//Create nuke account icon
|
|
const nukeAccount = document.createElement('i');
|
|
nukeAccount.classList.add("bi-radioactive","admin-user-list-icon","admin-user-list-unban-icon");
|
|
nukeAccount.id = `admin-user-list-unban-icon-${user.user}`;
|
|
nukeAccount.title = `Nuke accounts`;
|
|
nukeAccount.addEventListener("click",console.log);
|
|
|
|
//If our user has been deleted don't return the nuke icon
|
|
return (user.deleted ? unbanIcon : [unbanIcon, nukeAccount]);
|
|
}
|
|
}
|
|
|
|
class adminTokeCommandList{
|
|
constructor(){
|
|
this.tokeCommandList = document.querySelector('div.toke-command-list');
|
|
this.newTokeCommandPrompt = document.querySelector('#new-toke-command-input');
|
|
this.newTokeCommandButton = document.querySelector('#new-toke-command-button');
|
|
|
|
//Setup input
|
|
this.setupInput();
|
|
|
|
//Pull the toke list on load
|
|
this.getTokeList();
|
|
}
|
|
|
|
setupInput(){
|
|
this.newTokeCommandButton.addEventListener('click', this.addToke.bind(this));
|
|
}
|
|
|
|
async addToke(event){
|
|
//Send out the new toke command and get the new list
|
|
const tokeList = await adminUtil.addTokeCommand(this.newTokeCommandPrompt.value);
|
|
//clear the prompt
|
|
this.newTokeCommandPrompt.value = "";
|
|
//render the returned list
|
|
this.renderTokeList(tokeList);
|
|
}
|
|
|
|
async getTokeList(){
|
|
const tokeList = await adminUtil.getTokeCommands();
|
|
this.renderTokeList(tokeList);
|
|
}
|
|
|
|
clearTokeList(){
|
|
this.tokeCommandList.innerHTML = "";
|
|
}
|
|
|
|
async deleteToke(event){
|
|
const name = event.target.id.replace("toke-command-delete-","");
|
|
|
|
const tokeList = await adminUtil.deleteTokeCommand(name);
|
|
|
|
this.renderTokeList(tokeList);
|
|
}
|
|
|
|
renderTokeList(tokeList){
|
|
if(tokeList != null){
|
|
//Clear our the toke list
|
|
this.clearTokeList();
|
|
|
|
//For each toke in the received list
|
|
tokeList.forEach((toke)=>{
|
|
//generate a toke command span, and append it to the toke list div
|
|
this.tokeCommandList.appendChild(this.generateTokeSpan(toke));
|
|
});
|
|
}
|
|
}
|
|
|
|
generateTokeSpan(toke){
|
|
//Create toke command span
|
|
const tokeSpan = document.createElement('span');
|
|
tokeSpan.classList.add('toke-command-list');
|
|
|
|
//Create toke command label
|
|
const tokeLabel = document.createElement('p');
|
|
tokeLabel.innerHTML = `!${toke}`;
|
|
tokeLabel.classList.add('toke-command-list');
|
|
|
|
//Create toke command delete icon
|
|
const tokeDelete = document.createElement('i');
|
|
tokeDelete.classList.add('toke-command-list', 'bi-trash-fill', 'toke-command-delete');
|
|
tokeDelete.id = `toke-command-delete-${toke}`;
|
|
tokeDelete.addEventListener('click', this.deleteToke.bind(this));
|
|
|
|
//append span contents to tokeSpan
|
|
tokeSpan.appendChild(tokeLabel);
|
|
tokeSpan.appendChild(tokeDelete);
|
|
|
|
//return the toke span
|
|
return tokeSpan
|
|
}
|
|
}
|
|
|
|
class adminEmoteList{
|
|
constructor(){
|
|
this.linkPrompt = document.querySelector('#new-emote-link-input');
|
|
this.namePrompt = document.querySelector('#new-emote-name-input');
|
|
this.addButton = document.querySelector('#new-emote-button');
|
|
this.emoteList = document.querySelector('#emote-list');
|
|
|
|
//Setup input
|
|
this.setupInput();
|
|
|
|
//Pull and render emote list
|
|
this.updateList();
|
|
}
|
|
|
|
setupInput(){
|
|
this.addButton.addEventListener('click', this.addEmote.bind(this));
|
|
}
|
|
|
|
async deleteEmote(event){
|
|
//Strip name from element id
|
|
const name = event.target.id.replace('emote-list-delete-','');
|
|
|
|
//Delete emote and pull list
|
|
const list = await adminUtil.deleteEmote(name);
|
|
|
|
//If we received a list
|
|
if(list != null){
|
|
//Pass updated liste to renderEmoteList function instead of pulling it twice
|
|
this.renderEmoteList(list);
|
|
}
|
|
}
|
|
|
|
async addEmote(event){
|
|
//Add emote to list and ingest returned updates list
|
|
const list = await adminUtil.addEmote(this.namePrompt.value, this.linkPrompt.value);
|
|
|
|
//If we received a list
|
|
if(list != null){
|
|
//Pass updated liste to renderEmoteList function instead of pulling it twice
|
|
this.renderEmoteList(list);
|
|
|
|
//Clear out the prompts
|
|
this.namePrompt.value = '';
|
|
this.linkPrompt.value = '';
|
|
}
|
|
}
|
|
|
|
async updateList(){
|
|
const list = await adminUtil.getEmotes();
|
|
this.renderEmoteList(list);
|
|
}
|
|
|
|
renderEmoteList(list){
|
|
//Clear the current list
|
|
this.emoteList.innerHTML = "";
|
|
|
|
//For each emote in the list
|
|
list.forEach((emote) => {
|
|
//Create span to hold emote
|
|
const emoteDiv = document.createElement('div');
|
|
emoteDiv.classList.add('emote-list-emote');
|
|
|
|
//If the emote is an image
|
|
if(emote.type == 'image'){
|
|
//Create image node
|
|
var emoteMedia = document.createElement('img');
|
|
//if emote is a video
|
|
}else if(emote.type == 'video'){
|
|
//create video node
|
|
var emoteMedia = document.createElement('video');
|
|
//Set video properties
|
|
emoteMedia.autoplay = true;
|
|
emoteMedia.muted = true;
|
|
emoteMedia.controls = false;
|
|
emoteMedia.loop = true;
|
|
}
|
|
|
|
//set media link as source
|
|
emoteMedia.src = emote.link;
|
|
//Set media class
|
|
emoteMedia.classList.add('emote-list-media');
|
|
|
|
//Create title span
|
|
const titleSpan = document.createElement('span');
|
|
titleSpan.classList.add('emote-list-title');
|
|
|
|
//Create paragraph tag
|
|
const emoteTitle = document.createElement('p');
|
|
//Set title class
|
|
emoteTitle.classList.add('emote-list-title');
|
|
//Set emote title
|
|
emoteTitle.innerHTML = `[${emote.name}]`;
|
|
|
|
//Create delete icon
|
|
const deleteIcon = document.createElement('i');
|
|
//Set delete icon id and class
|
|
deleteIcon.classList.add('bi-trash-fill', 'emote-list-delete');
|
|
deleteIcon.id = `emote-list-delete-${emote.name}`;
|
|
//Add delete icon event listener
|
|
deleteIcon.addEventListener('click',this.deleteEmote.bind(this));
|
|
|
|
|
|
//Add the emote media to the emote span
|
|
emoteDiv.appendChild(emoteMedia);
|
|
//Add title paragraph node
|
|
titleSpan.appendChild(emoteTitle);
|
|
//Add trash icon node
|
|
titleSpan.appendChild(deleteIcon);
|
|
//Add title span
|
|
emoteDiv.appendChild(titleSpan);
|
|
|
|
//Append the mote span to the emote list
|
|
this.emoteList.appendChild(emoteDiv);
|
|
});
|
|
}
|
|
}
|
|
|
|
const userList = new adminUserList();
|
|
const permissionList = new adminPermissionList();
|
|
const userBanList = new adminUserBanList();
|
|
const tokeCommandList = new adminTokeCommandList();
|
|
const emoteList = new adminEmoteList(); |