--[[ Modular Chatbox Command Interpreter Made by LDDestroier pastebin get ECeJ3CpL mcci --]] local progname = fs.getName(shell.getRunningProgram()) local baseDir = ".mcci" --all other directories are combined with this one, you see local commandDir = fs.combine(baseDir,"cmd") --a folder local commandHelpDir = fs.combine(baseDir,"cmdhelp") --a folder local chatlogDir = fs.combine(baseDir,"chatlog") --a file local banDir = fs.combine(baseDir,"banlist") local adminDir = fs.combine(baseDir,"adminlist") local configDir = fs.combine(baseDir,"config") local apiDir = fs.combine(baseDir,"statemachine") local _ local useMoarPeriphs = (peripheral.find("chatbox_admin") or peripheral.find("chatbox") or peripheral.find("chat_box")) and true or false --either moarperipherals/computronics xor peripherals++ if not fs.exists(commandDir) then fs.makeDir(commandDir) end if not fs.exists(commandHelpDir) then fs.makeDir(commandHelpDir) end if not statemachine then if fs.exists(apiDir) then os.loadAPI(apiDir) print("Loaded StateMachine API.") else print("Downloading StateMachine API...") local foyle = http.get("https://pastebin.com/raw/3qe1iUQc") if not foyle then print("Could not download StateMachine API...") return false else local file = fs.open(apiDir,"w") file.write(foyle.readAll()) file.close() os.loadAPI(apiDir) print("Loaded StateMachine API.") end end end local adminUserTable = { --only one who can use admin commands. default values. "EldidiStroyrr", "Notch", } if pairs ~= _G.pairs then pairs = function(tbl) if type(tbl) ~= "table" then error("expected a table here, but got "..type(tbl)) else return _G.pairs(tbl) end end end local doRestart = false local adminUser = function(u) if type(u) ~= "string" then return false end for a = 1, #adminUserTable do if adminUserTable[a]:lower() == u:lower() then return true end end return false end --[[ local getEvents = function(...) local arg = table.pack(...) local output while true do output = {os.pullEvent()} for a = 1, #arg do if output[1] == arg[a] then return table.unpack(output) end end end end --]] --here's a more experimental getEvents... getEvents = function(...) local arg, funky, output = table.pack(...), {} if #arg <= 1 then return os.pullEvent(table.unpack(arg)) else for a = 1, #arg do funky[a] = function() output = {os.pullEvent(arg[a])} end end parallel.waitForAny(table.unpack(funky)) return table.unpack(output) end end local mcci = { prefix = "(>", ignoreErrors = true, mcciLabel = "MCCI", } local chatbox local usingAdminChatbox = false local chatDelay = 0.1 --seconds. regular chatboxes in P+1 will error if you use them too fast local chatbox = peripheral.find("chatbox_admin") or peripheral.find("chatbox") or peripheral.find("chatBox") or peripheral.find("chat_box") local cLabel local setLabel = function(newLabel) cLabel = newLabel if chatbox.setLabel then chatbox.setLabel(newLabel) elseif chatbox.setName then chatbox.setName(newLabel) end end if useMoarPeriphs then setLabel(mcci.mcciLabel) end local SAY = chatbox.say local WHISPER = commands.tellraw and function(user,message) commands.exec("tellraw "..user..' {"text":"['..cLabel..'] '..message..'"}') end or chatbox.tell local LISTEN = function(username,checkPrefix) local evt local user, message while true do evt = {getEvents("chat_message","chatbox_command","chat")} user, message = evt[3 - (useMoarPeriphs and 0 or 1)], evt[4 - (useMoarPeriphs and 0 or 1)] if (user == (username or user)) then if checkPrefix then if message:sub(1,#tostring(checkPrefix)) == checkPrefix then message = message:sub(#tostring(checkPrefix)+1) break end else break end end end return user, message end local explode = function(div,str) if (div=='') then return false end local pos,arr = 0,{} for st,sp in function() return string.find(str,div,pos,false) end do table.insert(arr,string.sub(str,pos,st-1)) pos = sp + 1 end table.insert(arr,string.sub(str,pos)) return arr end local chatlog = {} local blacklist = {} local whitelist = {} local userActions = {} local userActionNames = {} local addToChatlog = function(user,message) if chatlog[1] then if chatlog[#chatlog][1] == user and chatlog[#chatlog][2] == message then chatlog[#chatlog] = {user,message,chatlog[#chatlog][3]+1} else chatlog[#chatlog+1] = {user,message,1} end else chatlog[#chatlog+1] = {user,message,1} end end local writeChatlog = function() prettyOutput = "("..chatlog[#chatlog][3] or "0"..") <"..chatlog[#chatlog][1].."> "..chatlog[#chatlog][2] local file = fs.open(chatlogDir,"a") file.writeLine(prettyOutput) file.close() end local saveBanlist = function() local file = fs.open(banDir,"w") for k,v in pairs(blacklist) do file.writeLine(k) file.writeLine(v) file.writeLine("") end file.close() end local loadBanlist = function() blacklist = {} local file = fs.open(banDir,"r") local name,reason = "" while name do name = file.readLine() if name then reason = file.readLine() file.readLine() --blank space blacklist[name] = reason end end file.close() return blacklist end local saveAdminlist = function() local file = fs.open(adminDir,"w") for a = 1, #adminUserTable do file.writeLine(adminUserTable[a]) end file.close() end local loadAdminlist = function() adminUserTable = {} local file = fs.open(adminDir,"r") local line = "" while line do line = file.readLine() if line then adminUserTable[#adminUserTable+1] = line end end file.close() return adminUserTable end saveConfig = function() local file = fs.open(configDir,"w") file.writeLine("prefix:"..textutils.serialize(mcci.prefix)) file.writeLine("mcciLabel:"..textutils.serialize(mcci.mcciLabel)) file.writeLine("ignoreErrors:"..tostring(mcci.ignoreErrors)) file.close() end local fixInput = function(input) if tonumber(input) then return tonumber(input) elseif input == "false" then return false elseif input == "true" then return true elseif textutils.unserialize(input) then return textutils.unserialize(input) else return tostring(input) end end loadConfig = function() local file = fs.open(configDir,"r") local line = "" while line do line = file.readLine() if line then local name = line:sub(1,line:find(":")-1) local value = line:sub(line:find(":")+1) value = fixInput(value) mcci[name] = value end end file.close() end local scr_x, scr_y = term.getSize() local render = statemachine.unblock(function() term.setTextColor(colors.white) term.setBackgroundColor(colors.black) term.clear() term.setCursorPos(1,1) print("("..mcci.mcciLabel.."/Prefix='"..mcci.prefix.."')") print("(ignoreErrors="..tostring(mcci.ignoreErrors)..")") print(("-"):rep(scr_x)) print("Active Users:") for k,v in pairs(userActions) do print("-"..k.." ("..userActionNames[k]..")") end end) local initCommand = function(u,cmd,...) local yarg = table.pack(...) local golly_G = setmetatable({}, {__index=(_ENV or getfenv())}) golly_G.chatbox = chatbox golly_G.WHISPER = WHISPER golly_G.SAY = SAY golly_G.LISTEN = LISTEN golly_G._USERNAME = u golly_G._PREFIX = mcci.prefix golly_G._ADMIN = adminUser(u) golly_G.pairs = pairs userActions[u] = statemachine.unblock(function() local func = loadfile(fs.combine(commandDir,cmd)) func = setfenv(func, golly_G) local output if mcci.ignoreErrors then output = {pcall( function() return func(table.unpack(yarg)) end )} else output = func(table.unpack(yarg)) end return table.unpack(output) end) userActionNames[u] = cmd render() end local dCommands,dCommandsHelp dCommandsHelp = { ["belay"] = mcci.prefix.."belay [playername] ; Belays all commands or a specific player's.", ["ban"] = mcci.prefix.."ban playername ; Bans a player from using this chat command interpreter.", ["unban"] = mcci.prefix.."unban playername ; Unbans a player who was banned from using this command interpreter.", ["banlist"] = mcci.prefix.."banlist ; Lists every player banned from using this command interpreter.", ["help"] = mcci.prefix.."help [commandname] ; Gives a more detailed description of a command.", ["update"] = mcci.prefix.."update ; Redownloads the MCCI program and overwrites the source if it's different.", ["restart"] = mcci.prefix.."restart ; Restarts the program. One would usually do this after updating.", ["checkadmin"] = mcci.prefix.."checkadmin [username] ; Without argument, checks if you are an admin. Also can check if another user is an admin.", ["admin"] = mcci.prefix.."admin username ; Sets a username as an administrator.", ["sudo"] = mcci.prefix.."sudo username command ; Runs a command as a specific user. Can't be used to abdicate.", ["abdicate"] = mcci.prefix.."abdicate ; Use with no arguments to tick yourself off the list of admins. Use with care, there's no confirming.", } dCommands = { --default commands ["belay"] = function(u,username) if adminUser(u) then if username then if userActions[username] then userActions[username] = nil WHISPER(u,username.."'s command has been belayed.") WHISPER(username,"Your command has been belayed. Ready.") render() else WHISPER(u,"That user isn't executing a cancelable command.") end else WHISPER(u,"All commands have been belayed.") for k,v in pairs(userActions) do WHISPER(k,"Your command has been belayed. Ready.") end userActions = {} render() end else WHISPER(k,"You can't do that!") end end, ["ban"] = function(u,baduser,...) local arg = table.pack(...) if adminUser(u) then if adminUser(baduser) then WHISPER(u,"Very funny. You can't ban admins.") elseif not baduser then WHISPER(u,"Do "..mcci.prefix.."ban [username]") else if blacklist[baduser] then WHISPER(u,"That dude was already banned.") else blacklist[baduser] = table.concat(arg," ") or "Unspecified reason." userActions[baduser] = nil userActionNames[baduser] = nil render() WHISPER(u,"'"..baduser.."' has been banned from making commands.") WHISPER(baduser,"You are hereby BANNED from making MCCI commands!") end end else WHISPER(u,"You can't do that!") end end, ["banlist"] = function(u) WHISPER(u,"## USERS BANNED FROM THIS CHAT AI ##") for k,v in pairs(blacklist) do WHISPER(u," * "..k.." ("..v..")") end end, ["unban"] = function(u,baduser) if adminUser(u) then if adminUser(baduser) then WHISPER(u,"Admins can't be banned to begin with, you dolt.") elseif (not baduser) then WHISPER(u,"Do "..mcci.prefix.."unban [username]") else if (not blacklist[baduser]) then WHISPER(u,"That dude isn't banned.") else blacklist[baduser] = nil WHISPER(u,"'"..baduser.."' has been unbanned from making commands.") WHISPER(baduser,"You've been unbanned from making commands.") end end else if blacklist[gooduser] then WHISPER(u,"You can't do that!") else WHISPER(u,"You couldn't do that even if "..gooduser.." were banned!") end end end, ["order"] = function(u) WHISPER(u,"What, do I look like a pizzeria to you?") end, ["help"] = function(u,cmd) if cmd then if dCommands[cmd] then WHISPER(u,dCommandsHelp[cmd]) else if not fs.exists(fs.combine(commandDir,cmd)) then WHISPER(u,"That command doesn't exist, internally or externally.") else if fs.exists(fs.combine(commandHelpDir,cmd)) then local file = fs.open(fs.combine(commandHelpDir,cmd),"r") local cont = file.readAll(file) file.close() cont = cont:gsub("$PREFIX",mcci.prefix):gsub("\n"," ; ") WHISPER(u,cont) else WHISPER(u,"That external command does not have a help file.") end end end else local list = {} local cmdlist = fs.list(commandDir) for k,v in pairs(dCommands) do list[#list+1] = k end for a = 1, #cmdlist do list[#list+1] = cmdlist[a] end for a = 1, #list do WHISPER(u,mcci.prefix..list[a]) end end end, ["update"] = function(u) if adminUser(u) then WHISPER(u,"Downloading latest version...") local foyle = http.get("https://pastebin.com/raw/ECeJ3CpL") if foyle then local cont = foyle.readAll() local mccifoyle = fs.open(shell.getRunningProgram(),"r") local mccicont = mccifoyle.readAll() mccifoyle.close() if mccicont == cont then WHISPER(u,"You were using the latest version. Aborting.") else local file = fs.open(shell.getRunningProgram(),"w") file.write(cont) file.close() WHISPER(u,"Update complete. Use "..mcci.prefix.."restart to run the latest version.") end end else WHISPER(u,"Yeah, try that again as an admin, doofus.") end end, ["restart"] = function(u) if adminUser(u) then doRestart = true end end, ["checkadmin"] = function(u,user) if not user then if adminUser(u) then WHISPER(u,"You're an admin.") else WHISPER(u,"You're decidedly not an admin.") end else if adminUser(user) then WHISPER(u,"'"..user.."' is an admin.") else WHISPER(u,"'"..user.."' is decidedly not an admin.") end end end, ["admin"] = function(u,user) if not user then if adminUser(u) then WHISPER(u,"Expected an argument with who you wish to make an admin, dunce.") else WHISPER(u,"Only admins can make other people admins, you twat.") end else if adminUser(u) then if adminUser(user) then if user == u then WHISPER(u,"Oh my god, stop it.") else WHISPER(u,"That person is already an admin.") end else table.insert(adminUserTable,user) WHISPER(u,"'"..user.."' is now an admin.") end else WHISPER(u,"This is an admin command, lamebrain.") end end end, ["abdicate"] = function(u,user) if adminUser(u) then if user then WHISPER(u,"You can't abdicate another user from their adminship.") else for a = 1, #adminUserTable do if adminUserTable[a] == u then table.remove(adminUserTable,a) WHISPER(u,"Lo, you have abdicated the throne of adminship.") return end end WHISPER(u,"That's weird. You weren't on the list, but you still did this command.") end else if user then WHISPER(u,"If you really don't like an admin, don't go complaining to me.") else WHISPER(u,"Abdicate from what? Only admins can do this!") end end end, ["sudo"] = function(u,user,cmd,...) if adminUser(u) then if cmd == "abdicate" then WHISPER(u,"You can't use sudo to abdicate people!") else if user then if userActions[user] then WHISPER(u,"That user is already in a command.") else if cmd then initCommand(user,cmd,...) WHISPER(u,"Ran '"..cmd.."' as '"..user.."'.") else WHISPER(u,"Sudo requires a second argument for the command.") end end else WHISPER(u,"Sudo requires a first argument for the user.") end end else WHISPER(u,"Sudo is an admin's command!") end end, } local handleDcommands = function(u,f,...) --heh heh heh local arg = table.pack(...) if dCommands[f] then return true, dCommands[f](u,table.unpack(arg)) else return false end end local handleNormalCommands = function(u,cmd,...) local yarg = table.pack(...) local cmdlist = fs.list(commandDir) for a = 1, #cmdlist do if cmdlist[a] == cmd then initCommand(u,cmd,table.unpack(yarg)) if useMoarPeriphs then setLabel(mcci.mcciLabel..":"..userActionNames[u]) end return true end end return false end local fuckingDoIt = statemachine.unblock(function() local user, message, isDcommand, isNormalCommand while true do user, message = LISTEN() sleep(0) --to ensure that chatbox output comes AFTER player chat addToChatlog(user,message) writeChatlog() loadBanlist() loadAdminlist() loadConfig() if useMoarPeriphs then setLabel(mcci.mcciLabel) end render() if not blacklist[user] then if message:sub(1,#mcci.prefix) == mcci.prefix then message = message:sub(#mcci.prefix+1) if userActions[user] then if message == "nvm" then userActions[user] = nil userActionNames[user] = nil WHISPER(user,"Command cancelled. Ready.") render() else if useMoarPeriphs then setLabel(mcci.mcciLabel..":"..userActionNames[user]) end os.queueEvent("followup_command_"..user,message) end else isNormalCommand = handleNormalCommands(user,table.unpack(explode(" ",message))) if not isNormalCommand then isDcommand = handleDcommands(user,table.unpack(explode(" ",message))) if doRestart then return else if not isDcommand then WHISPER(user,"No such command exists, internally or externally.") end end end end end saveBanlist() saveAdminlist() saveConfig() end end end) if not fs.exists(adminDir) then saveAdminlist() end if not fs.exists(banDir) then saveBanlist() end if not fs.exists(configDir) then saveConfig() end loadAdminlist() loadBanlist() loadConfig() render() local rootEvent = {} local getRootEvent = function() rootEvent = {os.pullEvent()} end --this used to be a function called doUserActions local doBreak, vres local grodyList statemachine.setLoop(function() fuckingDoIt() getRootEvent() local vres grodyList = {} --if #rootEvent > 0 then os.queueEvent(table.unpack(rootEvent)) end for k,v in pairs(userActions) do vres = nil if v then vres = v() end if vres then if useMoarPeriphs then setLabel(mcci.mcciLabel) end WHISPER(k,"Ready.") grodyList[#grodyList+1] = k end end for a = 1, #grodyList do userActions[grodyList[a]] = nil userActionNames[grodyList[a]] = nil end render() return doRestart end) statemachine.run() if doRestart then SAY("MCCI Restarting.") shell.run(shell.getRunningProgram()) end