Finished up with chat prompt autocomplete.
This commit is contained in:
parent
acbe0400c4
commit
9df7f52e9e
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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){
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue