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.sendChanEmotes(chanDB);
await this.sendPersonalEmotes(userDB);
//Send out used tokes
await this.sendUsedTokes(userDB);
//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.
//Having to crawl through these sockets is that. Because the other ways seem more gross somehow.
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
@ -73,11 +80,33 @@ module.exports = class{
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
const flairListDB = await flairModel.find({});
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
const userObj = {
id: this.id,
@ -85,7 +114,11 @@ module.exports = class{
rank: this.rank,
chanRank: this.chanRank,
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

View file

@ -447,6 +447,17 @@ channelSchema.methods.getChannelRank = async function(user){
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){
//Get site-wide rank as number, default to anon for anonymous users
const rank = userDB ? permissionModel.rankToNum(userDB.rank) : permissionModel.rankToNum("anon");
@ -464,15 +475,25 @@ channelSchema.methods.permCheckByUserDoc = async function(userDB, perm){
return (permCheck || overrideCheck);
}
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;
}
channelSchema.methods.getPermMapByUserDoc = async function(userDB){
//Grap site-wide permissions
const sitePerms = await permissionModel.getPerms();
const siteMap = sitePerms.getPermMapByUserDoc(userDB);
//Pull chan permissions keys
let permTree = channelPermissionSchema.tree;
let permMap = new Map();
return await this.permCheckByUserDoc(userDB, perm)
//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 perm map
return {
site: siteMap.site,
chan: permMap
};
}
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){
//Get permission list
const perms = await this.getPerms();
//Set user to anon rank if no rank was found for the given user
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!`);
}
//Call the perm check method
return perms.permCheckByUserDoc(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){
//Get permission list
const perms = (await this.getPerms()).channelOverrides;
//Set user to anon rank if no rank was found for the given user
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!`);
}
const perms = await this.getPerms();
//Call the perm check method
return perms.overrideCheckByUserDoc(user, perm);
}
//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);

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
function loadPermValidatorSchema(){
//Pull permissions keys
var tempPerms = permissionModel.schema.tree;
var permTree = permissionModel.schema.tree;
//Create empty object for schema
var schema = {};
//Scrape out gunk
delete tempPerms.id;
delete tempPerms._id;
delete tempPerms.__v;
delete tempPerms.channelOverrides;
delete permTree.id;
delete permTree._id;
delete permTree.__v;
delete permTree.channelOverrides;
//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
schema[`permissionsMap.${key}`] = {
optional: true,
@ -49,7 +49,7 @@ function loadPermValidatorSchema(){
options: module.exports.isRank
}
}
});
}
//return the auto-generated schema
return schema;
@ -57,17 +57,17 @@ function loadPermValidatorSchema(){
function loadChanPermValidatorSchema(){
//Pull permissions keys
var tempPerms = channelPermissionSchema.tree;
var permTree = channelPermissionSchema.tree;
//Create empty object for schema
var schema = {};
//Scrape out gunk
delete tempPerms.id;
delete tempPerms._id;
delete tempPerms.__v;
delete permTree.id;
delete permTree._id;
delete permTree.__v;
//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
schema[`channelPermissionsMap.${key}`] = {
optional: true,
@ -75,7 +75,7 @@ function loadChanPermValidatorSchema(){
options: module.exports.isRank
}
}
});
}
//return the schema
return schema;

View file

@ -58,12 +58,15 @@ class channel{
}
handleClientInfo(data){
this.user = {
id: data.user.id,
name: data.user.name,
rank: data.user.rank
}
//Ingest user data
this.user = data.user;
//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);
}
}

View file

@ -143,7 +143,10 @@ class chatBox{
send(event){
if((!event || !event.key || event.key == "Enter") && this.chatPrompt.value){
this.commandPreprocessor.preprocess(this.chatPrompt.value);
//Clear our prompt and autocomplete nodes
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
//and also directly push it into a shared array :P
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
if(word == '' ? false : definition.indexOf(word) == 0){
//Append the proper prefix/postfix to the current command
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
matches.push(definition);
}

View file

@ -170,7 +170,9 @@ class commandPreprocessor{
tokes: {
prefix: '!',
postfix: '',
cmds: this.usedTokes
cmds: [
['toke', true]
].concat(injectPerms(this.usedTokes))
},
//Make sure to add spaces at the end for commands that take arguments
//Not necissary but definitely nice to have
@ -178,50 +180,48 @@ class commandPreprocessor{
prefix: '!',
postfix: '',
cmds: [
"whisper ",
"announce ",
"serverannounce ",
"clear ",
"kick "
["whisper ", true],
["announce ", client.user.permMap.chan.get('announce')],
["serverannounce ", client.user.permMap.site.get('announce')],
["clear ", client.user.permMap.chan.get('clearChat')],
["kick ", client.user.permMap.chan.get('kickUser')],
]
},
localCMD:{
prefix: '/',
postfix: '',
cmds: [
"high "
["high ", true]
]
},
usernames:{
prefix: '',
postfix: '',
cmds: Array.from(client.userList.colorMap.keys())
cmds: injectPerms(Array.from(client.userList.colorMap.keys()))
},
emotes:{
prefix:'[',
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 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;
}
}
}