Finished up with chat prompt autocomplete.

This commit is contained in:
rainbow napkin 2025-01-05 00:05:19 -05:00
parent acbe0400c4
commit 9df7f52e9e
7 changed files with 197 additions and 100 deletions

View file

@ -41,6 +41,8 @@ module.exports = class{
await this.sendSiteEmotes(); await this.sendSiteEmotes();
await this.sendChanEmotes(chanDB); await this.sendChanEmotes(chanDB);
await this.sendPersonalEmotes(userDB); await this.sendPersonalEmotes(userDB);
//Send out used tokes
await this.sendUsedTokes(userDB); await this.sendUsedTokes(userDB);
//Tattoo hashed IP address to user account for seven days //Tattoo hashed IP address to user account for seven days
@ -64,7 +66,12 @@ module.exports = class{
//at the end of the day there has to be some penance for decent multi-session handling on-top of a library that doesn't do it. //at the end of the day there has to be some penance for decent multi-session handling on-top of a library that doesn't do it.
//Having to crawl through these sockets is that. Because the other ways seem more gross somehow. //Having to crawl through these sockets is that. Because the other ways seem more gross somehow.
emit(eventName, args){ emit(eventName, args){
this.socketCrawl((socket)=>{socket.emit(eventName, args)}); this.socketCrawl((socket)=>{
//Ensure our socket is initialized
if(socket != null){
socket.emit(eventName, args);
}
});
} }
//generic disconnect function, defaults to kick //generic disconnect function, defaults to kick
@ -73,11 +80,33 @@ module.exports = class{
this.socketCrawl((socket)=>{socket.disconnect()}); this.socketCrawl((socket)=>{socket.disconnect()});
} }
async sendClientMetadata(){ //This is the big first push upon connection
//It should only fire once, so things that only need to be sent once can be slapped into here
async sendClientMetadata(userDB, chanDB){
//Get flairList from DB and setup flairList array //Get flairList from DB and setup flairList array
const flairListDB = await flairModel.find({}); const flairListDB = await flairModel.find({});
var flairList = []; var flairList = [];
//if we wherent handed a user document
if(userDB == null){
//Pull it based on user name
userDB = await userModel.findOne({user: this.user});
}
//if we wherent handed a channel document
if(chanDB == null){
//Pull it based on channel name
chanDB = await channelModel.findOne({name: this.channel.name});
}
//If our perm map is un-initiated
//can't set this in constructor easily since it's asyncornous
//need to wait for it to complete before sending this off, but shouldnt re-do the wait for later connections
if(this.permMap == null){
//Grab perm map
this.permMap = await chanDB.getPermMapByUserDoc(userDB);
}
//Setup our userObj //Setup our userObj
const userObj = { const userObj = {
id: this.id, id: this.id,
@ -85,7 +114,11 @@ module.exports = class{
rank: this.rank, rank: this.rank,
chanRank: this.chanRank, chanRank: this.chanRank,
highLevel: this.highLevel, highLevel: this.highLevel,
flair: this.flair permMap: {
site: Array.from(this.permMap.site),
chan: Array.from(this.permMap.chan),
},
flair: this.flair,
} }
//For each flair listed in the Database //For each flair listed in the Database

View file

@ -447,6 +447,17 @@ channelSchema.methods.getChannelRank = async function(user){
return await this.getChannelRankByUserDoc(userDB); return await this.getChannelRankByUserDoc(userDB);
} }
channelSchema.methods.permCheck = async function (user, perm){
//Set userDB to null if we wheren't passed a real user
if(user != null){
var userDB = await userModel.findOne({user: user.user});
}else{
var userDB = null;
}
return await this.permCheckByUserDoc(userDB, perm)
}
channelSchema.methods.permCheckByUserDoc = async function(userDB, perm){ channelSchema.methods.permCheckByUserDoc = async function(userDB, perm){
//Get site-wide rank as number, default to anon for anonymous users //Get site-wide rank as number, default to anon for anonymous users
const rank = userDB ? permissionModel.rankToNum(userDB.rank) : permissionModel.rankToNum("anon"); const rank = userDB ? permissionModel.rankToNum(userDB.rank) : permissionModel.rankToNum("anon");
@ -464,15 +475,25 @@ channelSchema.methods.permCheckByUserDoc = async function(userDB, perm){
return (permCheck || overrideCheck); return (permCheck || overrideCheck);
} }
channelSchema.methods.permCheck = async function (user, perm){ channelSchema.methods.getPermMapByUserDoc = async function(userDB){
//Set userDB to null if we wheren't passed a real user //Grap site-wide permissions
if(user != null){ const sitePerms = await permissionModel.getPerms();
var userDB = await userModel.findOne({user: user.user}); const siteMap = sitePerms.getPermMapByUserDoc(userDB);
}else{ //Pull chan permissions keys
var userDB = null; let permTree = channelPermissionSchema.tree;
let permMap = new Map();
//For each object in the temporary permissions object
for(let perm of Object.keys(permTree)){
//Check the current permission
permMap.set(perm, await this.permCheckByUserDoc(userDB, perm));
} }
return await this.permCheckByUserDoc(userDB, perm) //return perm map
return {
site: siteMap.site,
chan: permMap
};
} }
channelSchema.methods.checkBanByUserDoc = async function(userDB){ channelSchema.methods.checkBanByUserDoc = async function(userDB){

View file

@ -139,26 +139,8 @@ permissionSchema.statics.permCheck = async function(user, perm){
permissionSchema.statics.permCheckByUserDoc = async function(user, perm){ permissionSchema.statics.permCheckByUserDoc = async function(user, perm){
//Get permission list //Get permission list
const perms = await this.getPerms(); const perms = await this.getPerms();
//Call the perm check method
//Set user to anon rank if no rank was found for the given user return perms.permCheckByUserDoc(user, perm);
if(user == null || user.rank == null){
user ={
rank: "anon"
};
}
//Check if this permission exists
if(perms[perm] != null){
//if so get required rank as a number
requiredRank = this.rankToNum(perms[perm])
//if so get user rank as a number
userRank = user ? this.rankToNum(user.rank) : 0;
//return whether or not the user is equal to or higher than the required rank for this permission
return (userRank >= requiredRank);
}else{
//if not scream and shout
throw new Error(`Permission check '${perm}' not found!`);
}
} }
permissionSchema.statics.overrideCheck = async function(user, perm){ permissionSchema.statics.overrideCheck = async function(user, perm){
@ -175,27 +157,9 @@ permissionSchema.statics.overrideCheck = async function(user, perm){
permissionSchema.statics.overrideCheckByUserDoc = async function(user, perm){ permissionSchema.statics.overrideCheckByUserDoc = async function(user, perm){
//Get permission list //Get permission list
const perms = (await this.getPerms()).channelOverrides; const perms = await this.getPerms();
//Call the perm check method
//Set user to anon rank if no rank was found for the given user return perms.overrideCheckByUserDoc(user, perm);
if(user == null || user.rank == null){
user ={
rank: "anon"
};
}
//Check if this permission exists
if(perms[perm] != null){
//if so get required rank as a number
requiredRank = this.rankToNum(perms[perm])
//if so get user rank as a number
userRank = user ? this.rankToNum(user.rank) : 0;
//return whether or not the user is equal to or higher than the required rank for this permission
return (userRank >= requiredRank);
}else{
//if not scream and shout
throw new Error(`Permission check '${perm}' not found!`);
}
} }
//Middleware for rank checks //Middleware for rank checks
@ -211,4 +175,76 @@ permissionSchema.statics.reqPermCheck = function(perm){
} }
} }
//methods
//these are good to have even for single-doc collections since we can loop through them without finding them in the database each time
permissionSchema.methods.permCheckByUserDoc = function(userDB, perm){
//Set user to anon rank if no rank was found for the given user
if(userDB == null || userDB.rank == null){
userDB ={
rank: "anon"
};
}
//Check if this permission exists
if(this[perm] != null){
//if so get required rank as a number
requiredRank = this.model().rankToNum(this[perm])
//if so get user rank as a number
userRank = userDB ? this.model().rankToNum(userDB.rank) : 0;
//return whether or not the user is equal to or higher than the required rank for this permission
return (userRank >= requiredRank);
}else{
//if not scream and shout
throw new Error(`Permission check '${perm}' not found!`);
}
}
permissionSchema.methods.overrideCheckByUserDoc = function(userDB, perm){
//Set user to anon rank if no rank was found for the given user
if(userDB == null || userDB.rank == null){
userDB ={
rank: "anon"
};
}
//Check if this permission exists
if(this.channelOverrides[perm] != null){
//if so get required rank as a number
requiredRank = this.model().rankToNum(this.channelOverrides[perm])
//if so get user rank as a number
userRank = userDB ? this.model().rankToNum(userDB.rank) : 0;
//return whether or not the user is equal to or higher than the required rank for this permission
return (userRank >= requiredRank);
}else{
//if not scream and shout
throw new Error(`Permission check '${perm}' not found!`);
}
}
permissionSchema.methods.getPermMapByUserDoc = function(userDB){
//Pull permissions keys
let permTree = this.schema.tree;
let overrideTree = channelPermissionSchema.tree;
let permMap = new Map();
let overrideMap = new Map();
//For each object in the temporary permissions object
for(let perm of Object.keys(permTree)){
//Check the current permission
permMap.set(perm, this.permCheckByUserDoc(userDB, perm));
}
//For each object in the temporary permissions object
for(let perm of Object.keys(overrideTree)){
//Check the current permission
overrideMap.set(perm, this.overrideCheckByUserDoc(userDB, perm));
}
//return the auto-generated schema
return {
site: permMap,
channelOverrides: overrideMap
}
}
module.exports = mongoose.model("permissions", permissionSchema); module.exports = mongoose.model("permissions", permissionSchema);

View file

@ -30,18 +30,18 @@ module.exports.isRank = function(value){
//Internal functions for loading validator schema through the database so we only have to maintain permissions in one place //Internal functions for loading validator schema through the database so we only have to maintain permissions in one place
function loadPermValidatorSchema(){ function loadPermValidatorSchema(){
//Pull permissions keys //Pull permissions keys
var tempPerms = permissionModel.schema.tree; var permTree = permissionModel.schema.tree;
//Create empty object for schema //Create empty object for schema
var schema = {}; var schema = {};
//Scrape out gunk //Scrape out gunk
delete tempPerms.id; delete permTree.id;
delete tempPerms._id; delete permTree._id;
delete tempPerms.__v; delete permTree.__v;
delete tempPerms.channelOverrides; delete permTree.channelOverrides;
//For each object in the temporary permissions object //For each object in the temporary permissions object
Object.keys(tempPerms).forEach((key) => { for(let key of Object.keys(permTree)){
//Create an entry in the validation schema for the current permission //Create an entry in the validation schema for the current permission
schema[`permissionsMap.${key}`] = { schema[`permissionsMap.${key}`] = {
optional: true, optional: true,
@ -49,7 +49,7 @@ function loadPermValidatorSchema(){
options: module.exports.isRank options: module.exports.isRank
} }
} }
}); }
//return the auto-generated schema //return the auto-generated schema
return schema; return schema;
@ -57,17 +57,17 @@ function loadPermValidatorSchema(){
function loadChanPermValidatorSchema(){ function loadChanPermValidatorSchema(){
//Pull permissions keys //Pull permissions keys
var tempPerms = channelPermissionSchema.tree; var permTree = channelPermissionSchema.tree;
//Create empty object for schema //Create empty object for schema
var schema = {}; var schema = {};
//Scrape out gunk //Scrape out gunk
delete tempPerms.id; delete permTree.id;
delete tempPerms._id; delete permTree._id;
delete tempPerms.__v; delete permTree.__v;
//For each object in the temporary permissions object //For each object in the temporary permissions object
Object.keys(tempPerms).forEach((key) => { for(let key of Object.keys(permTree)){
//Create an entry in the validation schema for the current permission //Create an entry in the validation schema for the current permission
schema[`channelPermissionsMap.${key}`] = { schema[`channelPermissionsMap.${key}`] = {
optional: true, optional: true,
@ -75,7 +75,7 @@ function loadChanPermValidatorSchema(){
options: module.exports.isRank options: module.exports.isRank
} }
} }
}); }
//return the schema //return the schema
return schema; return schema;

View file

@ -58,12 +58,15 @@ class channel{
} }
handleClientInfo(data){ handleClientInfo(data){
this.user = { //Ingest user data
id: data.user.id, this.user = data.user;
name: data.user.name,
rank: data.user.rank
}
//Re-hydrate permission maps
this.user.permMap.site = new Map(data.user.permMap.site);
this.user.permMap.chan = new Map(data.user.permMap.chan);
//Tell the chatbox to handle client info
//should it have its own event listener instead? Guess it's a stylistic choice :P
this.chatBox.handleClientInfo(data); this.chatBox.handleClientInfo(data);
} }
} }

View file

@ -143,7 +143,10 @@ class chatBox{
send(event){ send(event){
if((!event || !event.key || event.key == "Enter") && this.chatPrompt.value){ if((!event || !event.key || event.key == "Enter") && this.chatPrompt.value){
this.commandPreprocessor.preprocess(this.chatPrompt.value); this.commandPreprocessor.preprocess(this.chatPrompt.value);
//Clear our prompt and autocomplete nodes
this.chatPrompt.value = ""; this.chatPrompt.value = "";
this.autocompletePlaceholder.innerHTML = '';
this.autocompleteDisplay.innerHTML = '';
} }
} }
@ -198,11 +201,12 @@ class chatBox{
//I went with a for loop instead of a filter beacuse I wanted to pull the processed definition with pre/postfix //I went with a for loop instead of a filter beacuse I wanted to pull the processed definition with pre/postfix
//and also directly push it into a shared array :P //and also directly push it into a shared array :P
for(let cmd of dictionary[set].cmds){ for(let cmd of dictionary[set].cmds){
//Append the proper prefix/postfix to the current command
const definition = (`${dictionary[set].prefix}${cmd}${dictionary[set].postfix}`);
//if definition starts with the current word //Append the proper prefix/postfix to the current command
if(word == '' ? false : definition.indexOf(word) == 0){ const definition = (`${dictionary[set].prefix}${cmd[0]}${dictionary[set].postfix}`);
//if definition starts with the current word and the command is enabled
if((word == '' ? false : definition.indexOf(word) == 0) && cmd[1]){
//Add definition to match list //Add definition to match list
matches.push(definition); matches.push(definition);
} }

View file

@ -170,7 +170,9 @@ class commandPreprocessor{
tokes: { tokes: {
prefix: '!', prefix: '!',
postfix: '', postfix: '',
cmds: this.usedTokes cmds: [
['toke', true]
].concat(injectPerms(this.usedTokes))
}, },
//Make sure to add spaces at the end for commands that take arguments //Make sure to add spaces at the end for commands that take arguments
//Not necissary but definitely nice to have //Not necissary but definitely nice to have
@ -178,50 +180,48 @@ class commandPreprocessor{
prefix: '!', prefix: '!',
postfix: '', postfix: '',
cmds: [ cmds: [
"whisper ", ["whisper ", true],
"announce ", ["announce ", client.user.permMap.chan.get('announce')],
"serverannounce ", ["serverannounce ", client.user.permMap.site.get('announce')],
"clear ", ["clear ", client.user.permMap.chan.get('clearChat')],
"kick " ["kick ", client.user.permMap.chan.get('kickUser')],
] ]
}, },
localCMD:{ localCMD:{
prefix: '/', prefix: '/',
postfix: '', postfix: '',
cmds: [ cmds: [
"high " ["high ", true]
] ]
}, },
usernames:{ usernames:{
prefix: '', prefix: '',
postfix: '', postfix: '',
cmds: Array.from(client.userList.colorMap.keys()) cmds: injectPerms(Array.from(client.userList.colorMap.keys()))
}, },
emotes:{ emotes:{
prefix:'[', prefix:'[',
postfix:']', postfix:']',
cmds: this.getEmoteNames() cmds: injectPerms(this.getEmoteNames())
} }
}; };
//Ensure default toke command
//Check if 'toke' is the first registered toke
if(dictionary.tokes.cmds[0] != 'toke'){
//Find the current place of the 'toke' command, if any
const tokeIndex = dictionary.tokes.cmds.indexOf('toke');
//If the default command is present but is out of order
if(tokeIndex != -1){
//Splice it out
dictionary.tokes.cmds.splice(tokeIndex,1);
}
//Throw it into the beggining of the array
dictionary.tokes.cmds.unshift('toke');
}
//return our dictionary object //return our dictionary object
return dictionary; return dictionary;
function injectPerms(cmds, perm = true){
//Create empty array to hold cmds
let cmdSet = [];
//For each cmd
for(let cmd of cmds){
//Add the cmd with its perm to the cmdset
cmdSet.push([cmd, perm]);
}
//return the cmd set
return cmdSet;
}
} }
} }