This repository has been archived on 2020-05-03. You can view files and clone it, but cannot push or open issues or pull requests.
mcompletescript/dist-archive/mCompleteScript_v20.lua

700 lines
21 KiB
Lua
Raw Permalink Blame History

--[[ _ _ /~` _ _ _ _ | _ _|_ _ (~ _ _. _ _|_
| | |\_,(_)| | ||_)|(/_ | (/__)(_| ||_) | v21
| |
History: Mappy v1..17 2008-2009, v18 Jan 2010, v19 Dec 2012, v20 Jan 2013, v21 Aug 2013
For time/date formatting, see lua.org/pil/22.1.html
]]
-----------------------------------------------------------[ options ]
-- Configure features
bBlockInvisibleCharacters = false
bBlockIllegalSearches = false
bEnableCatchup = true
bEnableRegistration = true
bEnableUserLocations = false
bEnableImpersonate = false
bEnableChatLogging = true
bEnableGroupChat = true
il = 15 -- Number of chat log messages to keep in memory
tf = "[%H.%M]" -- Format of chat log timestamps
AdminLevel = 0 -- Permission required for admin features (by default 0 is 'Master')
RegLevel = 3 -- Permission given upon registration (by default 3 is 'Registered')
PLL = 5 -- Minimum password length (must be at least one)
-- Words that flag searches;
badwords = {"clop"}
-- Settings for chat logs
chatLogPath = Core.GetPtokaXPath().."logs/%B - %Y - 127.0.0.1.txt"
chatLogTimeFormat = "%Y-%m-%d %H:%M:%S"
-- Settings for group chats
groupChatPruneCheckEvery = 30
groupChatTimeoutAfter = 15*60
-- Strings
SZ_REG_CONNECTED = "Thank you for using a registered nickname!"
SZ_UNREG_CONNECTED = "Want to secure your nickname? Register it."
SZ_REG_COMPLETED = "Your nickname is now registered. In future, log on with your password."
SZ_INVISIBLE_CHAR = "Sorry, remove all invisible characters from your name to reconnect."
SZ_REDIR_REASON = "Rearranging network, please be patient..."
SZ_E_DISABLED = "* Feature disabled."
SZ_E_SYNTAX_ERROR = "* Syntax error."
SZ_E_UNAUTHORISED = "* Unauthorised."
SZ_E_SCRIPT_ERROR = "* Error performing script operation."
SZ_GCHAT_CLOSING = "## Closing group chat..."
SZ_GCHAT_AWAYMSG = "Away"
-----------------------------------------------------------[ data storage ]
groupchats = {}
groupChatTimer = nil
ml = {""}
mp = -1
hall = {
-- 1-indexed,
"Rochester and Rutherford", -- 8..15
"Ilam Apartments", -- 16..31
"Ilam Apartments",
"Bishop Julius", -- 32..39
"College House", -- 40..47
"University Hall", -- 48..55
"Sonada" -- 56..63
}
-----------------------------------------------------------[ connections ]
function OpConnected(user) SC(user) end
function RegConnected(user) SC(user) end
function UserConnected(user)
if bBlockInvisibleCharacters then
-- Only filters unregistered users
if string.find(user.sNick, "<EFBFBD>") then
Core.SendToUser(user, "* "..SZ_INVISIBLE_CHAR)
BanMan.TempBanNick(user.sNick, 1, "Use of invisible character", Core.GetHubSecAlias)
Core.SendToOps("<"..Core.GetHubSecAlias.."> "..user.sNick.." tried to use an invisible character!")
end
end
SC(user)
end
function SC(user, sendMessages)
-- CONTEXT_HUB = 0x01
-- CONTEXT_USER = 0x02
if sendMessages == nil then sendMessages = true end
if not user then return end
-- Clear existing
AddUSC(user, "255 3", "", "")
-- Locations
if (bEnableUserLocations) then
AddUSC(user, "1 2", "Get User Location", "!locate %[nick]")
end
-- Registration
if (bEnableRegistration) then
if user.iProfile == RegLevel then
if sendMessages then Core.SendToUser(user, "* "..SZ_REG_CONNECTED) end
AddUSC(user, "1 1", "Change your password...", "!reg %[line:Enter a password]")
else
if sendMessages then Core.SendToUser(user, "* "..SZ_UNREG_CONNECTED) end
AddUSC(user, "1 1", "Register your nick...", "!reg %[line:Enter a password]")
end
end
-- Catchup
if (bEnableCatchup) then
if sendMessages then SendCatchup(user) end
AddUSC(user, "1 1", "Get chat history", "!catchup")
end
-- Impersonation
if (bEnableImpersonate or user.iProfile == AdminLevel) then
AddUSC(user, "1 2", "Impersonate\\Say as user...", "!yuo %[nick] %[line:Enter text]")
AddUSC(user, "1 2", "Impersonate\\Action as user [normal]...", "!yuo %[nick] /me %[line:Enter text]")
AddUSC(user, "1 2", "Impersonate\\Action as user [system]...", "!you %[nick] %[line:Enter text]")
AddUSC(user, "0 2", "", "")
AddUSC(user, "1 3", "Impersonate\\Send system message...", "!you %[line:Enter text]")
end
-- Group chats
if (bEnableGroupChat) then
AddUSC(user, "1 1", "Group chat\\New group chat", "!groupchat_new")
local foundAnyChats = false
for botnick, grinfo in pairs(groupchats) do
if grinfo["owner"] == user.sNick then
if not foundAnyChats then
AddUSC(user, "1 1", "Group chat\\-", "")
foundAnyChats = true
end
AddUSC(user, "1 1", "Group chat\\Close chat ["..botnick.."]", "!groupchat_close "..botnick)
AddUSC(user, "1 2", "Invite user to join ["..botnick.."]", "!groupchat_invite "..botnick.." %[nick]")
end
end
end
-- Admin tasks
if (user.iProfile == AdminLevel) then
AddUSC(user, "1 1", "Admin\\Restart PtokaX", "!restart")
AddUSC(user, "1 1", "Admin\\Redirect all users...", "!redirectallusers %[line:Redirect address]")
AddUSC(user, "1 1", "Admin\\Set hub title...", "!sethubtitle %[line:Hub title]")
AddUSC(user, "1 1", "Admin\\Reload MOTD from disk", "!reload-motd")
AddUSC(user, "0 1", "Admin\\-", "")
AddUSC(user, "1 2", "Admin\\Look up user password", "!get-password %[nick]")
if bEnableCatchup then
AddUSC(user, "1 1", "Admin\\Flush catchup ring buffer", "!catchup-flush")
end
AddUSC(user, "1 1", "Admin\\Chat as "..Core.GetHubSecAlias().."...", "!yuo "..Core.GetHubSecAlias().." %[line:Enter text]")
AddUSC(user, "1 1", "Admin\\Get hub stats", "!get-stats")
AddUSC(user, "1 1", "Admin\\Refresh menu", "!refreshmenu")
-- Scripts menu
AddUSC(user, "0 1", "Admin\\-", "")
ScriptMan.Refresh()
for i,v in ipairs(ScriptMan.GetScripts()) do
local title = "Admin\\Scripts\\["..(v.bEnabled and "X" or " ").."] "..v.sName.."\\"
if (v.bEnabled) then
AddUSC(user, "1 1", title.."Disable", "!stop-script "..v.sName)
else
AddUSC(user, "1 1", title.."Enable", "!start-script "..v.sName)
end
AddUSC(user, "1 1", title.."Restart", "!restart-script "..v.sName)
end
end
end
-----------------------------------------------------------[ main parsing ]
triggers = {
-- Usage: !locate username
["!locate"] = function(user, data, t)
if not bEnableUserLocations then return Core.SendToUser(user, SZ_E_DISABLED) end
if #t ~= 2 then return Core.SendToUser(user, SZ_E_SYNTAX_ERROR) end
Core.SendToUser(user, GetHallFromIP(t[2]))
end,
-- Usage: !reg password
["!reg"] = function(user, data, t)
if not bEnableRegistration then return Core.SendToUser(user, SZ_E_DISABLED) end
if #t ~= 2 then return Core.SendToUser(user, SZ_E_SYNTAX_ERROR) end
if string.len(t[2])>=PLL then
if RegMan.GetReg(user.sNick)==nil then
RegMan.AddReg(user.sNick, t[2], RegLevel)
Core.SendToUser(user, "* "..SZ_REG_COMPLETED)
else
RegMan.ChangeReg(user.sNick, t[2], RegLevel)
Core.SendToUser(user, "* Password changed.")
end
RegMan.Save()
else
Core.SendToUser(user, "* Passwords must be at least "..PLL.." characters long.")
end
end,
-- Usage: !catchup
["!catchup"] = function(user, data, t)
if not bEnableCatchup then return Core.SendToUser(user, SZ_E_DISABLED) end
SendCatchup(user)
end,
-- Action as user
-- Usage: !you their-nick message goes here
["!you"] = function(user, data, t)
if bEnableImpersonate or user.iProfile == AdminLevel then
local m
_,m=s2(data)
_,m=s2(m)
Core.SendToAll("* "..m)
logCatchup("* "..m)
else
Core.SendToUser(user, SZ_E_DISABLED)
end
end,
-- Say as user
-- Usage: !yuo their-nick message goes here
["!yuo"] = function(user, data, t)
if bEnableImpersonate or user.iProfile == AdminLevel then
local m, u, mg
_,m=s2(data)
_,m=s2(m)
u,mg=s2(m)
Core.SendToAll("<"..u.."> "..mg)
logCatchup("<"..u.."> "..mg)
else
Core.SendToUser(user, Z_E_DISABLED)
end
end,
-- Initiate a group chat
-- Usage: !groupchat_new
["!groupchat_new"] = function(user, data, t)
if bEnableGroupChat or user.iProfile == AdminLevel then
if #t ~= 1 then return Core.SendToUser(user, SZ_E_SYNTAX_ERROR) end
groupchat_init(user.sNick)
else
Core.SendToUser(user, Z_E_DISABLED)
end
end,
-- Invite a user to a group chat under your control
-- Usage: !groupchat_invite botnick friendnick
["!groupchat_invite"] = function(user, data, t)
if bEnableGroupChat or user.iProfile == AdminLevel then
if #t ~= 3 then return Core.SendToUser(user, SZ_E_SYNTAX_ERROR) end
groupchat_invite(user, t[2], t[3])
else
Core.SendToUser(user, Z_E_DISABLED)
end
end,
-- Close a group chat under your control
-- Usage: !groupchat_close botnick
["!groupchat_close"] = function(user, data, t)
if bEnableGroupChat or user.iProfile == AdminLevel then
if #t ~= 2 then return Core.SendToUser(user, SZ_E_SYNTAX_ERROR) end
groupchat_end(user, t[2])
else
Core.SendToUser(user, Z_E_DISABLED)
end
end
}
adminTriggers = {
-- Usage: !get-password their-nick
["!get-password"] = function(user, data, t)
if #t ~= 2 then return Core.SendToUser(user, SZ_E_SYNTAX_ERROR) end
local reguser = RegMan.GetReg(t[2])
if reguser then
Core.SendToUser(user, "User "..reguser.sNick.." has password '"..reguser.sPassword.."'")
else
Core.SendToUser(user, "User "..t[2].." not registered.")
end
end,
-- Usage: !reload-motd
["!reload-motd"] = function(user, data, t)
local motd = readFile(Core.GetPtokaXPath().."cfg/Motd.txt", "rb")
if motd then
SetMan.SetMOTD(motd)
SetMan.Save()
Core.SendToUser(user, "<"..Core.GetHubSecAlias().."> "..SetMan.GetMOTD())
else
Core.SendToUser(user, "* Error opening '"..Core.GetPtokaXPath().."cfg/Motd.txt'")
end
end,
-- Usage: !redirectallusers target-address
["!redirectallusers"] = function(user, data, t)
if #t ~= 2 then return Core.SendToUser(user, SZ_E_SYNTAX_ERROR) end
local allUsers = Core.GetOnlineUsers()
for _, u in pairs(allUsers) do
Core.SendToUser(user, "* Redirecting ".. u.sNick .." to "..t[2])
Core.Redirect(u, t[2], SZ_REDIR_REASON)
end
end,
-- Usage: !refreshmenu
["!refreshmenu"] = function(user, data, t)
SC(user, false)
end,
-- Usage: !start-script filename.lua
["!start-script"] = function(user, data, t)
if #t ~= 2 then return Core.SendToUser(user, SZ_E_SYNTAX_ERROR) end
if (ScriptMan.StartScript(t[2]) == nil) then Core.SendToUser(user, SZ_E_SCRIPT_ERROR) end
Core.SendToUser(user, "* Script "..t[2].." started.");
SetMan.Save()
SC(user, false)
end,
-- Usage: !stop-script filename.lua
["!stop-script"] = function(user, data, t)
if #t ~= 2 then return Core.SendToUser(user, SZ_E_SYNTAX_ERROR) end
if (ScriptMan.StopScript(t[2]) == nil) then Core.SendToUser(user, SZ_E_SCRIPT_ERROR) end
Core.SendToUser(user, "* Script "..t[2].." stopped.");
SetMan.Save()
SC(user, false)
end,
-- Usage: !restart-script filename.lua
["!restart-script"] = function(user, data, t)
if #t ~= 2 then return Core.SendToUser(user, SZ_E_SYNTAX_ERROR) end
if (ScriptMan.RestartScript(t[2]) == nil) then Core.SendToUser(user, SZ_E_SCRIPT_ERROR) end
Core.SendToUser(user, "* Script "..t[2].." restarted.");
SetMan.Save()
SC(user, false)
end,
-- Usage: !get-stats
["!get-stats"] = function(user, data, t)
Core.SendToUser(user, "* Peak users this boot: "..Core.GetActualUsersPeak())
Core.SendToUser(user, "* Peak users all time: "..Core.GetMaxUsersPeak())
Core.SendToUser(user, "* Current share: "..Core.GetCurrentSharedSize())
Core.SendToUser(user, "* Uptime: "..Core.GetUpTime().." seconds")
end,
-- Usage: !catchup-flush
["!catchup-flush"] = function(user, data, t)
flushCatchup()
Core.SendToUser(user, "* Catchup flushed.")
end,
-- Usage: !sethubtitle title goes here
["!sethubtitle"] = function(user, data, t)
SetMan.SetString(0, table.concat(t, " ", 2))
SetMan.Save()
end,
}
function ChatArrival(user, data)
local validcommand=false
if (string.sub(data, 1, 1) == "<" ) then
data=string.sub(data, 1, string.len(data) -1)
t = {}
for k in string.gmatch(data, "%s+([^%s]+)") do
table.insert(t, k)
end
if (#t>=1 and triggers[t[1]]) then
triggers[t[1]](user, data, t)
return true
elseif (#t>=1 and user.iProfile == AdminLevel and adminTriggers[t[1]]) then
adminTriggers[t[1]](user, data, t)
return true
elseif t[1] and string.sub(t[1], 1, 1) == "!" then
-- Don't add potential system commands to catchup/logs
else
logCatchup(data:sub(1)) --, -2)) --string.len(data)))
logChat(nmdc_unescape(data:sub(1))) --, -2)))
end
end
end
function ToArrival(user, data)
-- Parse data and determine whether it's intended for any groupchat bots
local recipient = data:gsub("%$To: ([^%s]+) From: [^%s]+ %$<[^>]+> (.*)|", "%1")
local body = data:gsub("%$To: ([^%s]+) From: [^%s]+ %$<[^>]+> (.*)|", "%2")
-- Core.SendToUser(user, nmdc_escape(data))
-- Core.SendToUser(user, "got recipient ["..recipient.."] and body ["..body.."]")
if groupchats[recipient] then
groupchat_sayas(recipient, user.sNick, body)
return true -- abort
else
return false -- process normally
end
end
function OnStartup()
-- Create groupchat pruner
groupChatTimer = TmrMan.AddTimer(groupChatPruneCheckEvery * 1000, "groupchat_prune")
end
-----------------------------------------------------------[ impersonation ]
function s2(inp)
sp = string.find(inp, " ")
if sp==nil then sp=0 end
return string.sub(inp, 1, sp-1), string.sub(inp, sp+1)
end
-----------------------------------------------------------[ group chat ]
function groupchat_init(ownernick)
-- Generate a nick for the bot by adding numbers to the user's nick until
-- one is not currently in use
local suffix = 1
while true do
-- Core.GetUser doesn't include script bots
if Core.GetUser(ownernick .. suffix) == nil and not groupchats[ownernick..suffix] then
break
end
suffix = suffix + 1
if suffix > 100 then
Core.SendToNick(ownernick, "Error: Couldn't pick a group chat username")
return
end
end
local botnick = ownernick..suffix
-- Register bot
if not Core.RegBot(botnick, "", "", false) then
Core.SendToNick(ownernick, "Error: Couldn't start group chat")
return
end
-- Save all data
groupchats[botnick] = {
["owner"] = ownernick,
["last"] = os.time(),
["users"] = {
[ownernick] = true
}
}
-- Refresh usercommands
SC( Core.GetUser(ownernick), false )
groupchat_say(botnick, "## Joins: "..ownernick)
end
-- Owner invites a user into an existing group chat
function groupchat_invite(user, botnick, friendnick)
if groupchats[botnick] and groupchats[botnick]["owner"] == user.sNick then -- only owner can invite
if Core.GetUser(friendnick) then -- Check if user's online
-- Add user and notify participants
groupchats[botnick]["users"][friendnick] = true
-- Notify self of existing participants
for peer,_ in pairs(groupchats[botnick]["users"]) do
local isHost = ""
if (groupchats[botnick]["owner"] == peer) then
isHost = " (host)"
end
if peer ~= friendnick then
-- we go last
Core.SendPmToNick(friendnick, botnick, "## Joins: "..peer..isHost)
end
end
-- Notify existing participants (and self) of joining
groupchat_say(botnick, "## Joins: "..friendnick) -- updates mtime
else
Core.SendToUser(user, "Error: User not online")
end
else
Core.SendToUser(user, SZ_E_UNAUTHORISED) -- nonexistent or not yours
end
end
-- Pass user as well, so that administrators can prune groupchat bots.
function groupchat_end(user, botnick)
if groupchats[botnick] then
if groupchats[botnick]["owner"] == user.sNick or user.iProfile == AdminLevel then
-- End chat
groupchat_remove(botnick)
else
-- Not allowed
Core.SendToUser(user, SZ_E_UNAUTHORISED)
end
end
end
-- Every minute or so, prune group chats that havn't had any replies in a while.
function groupchat_prune()
if #groupchats then
for botnick, gcdata in pairs(groupchats) do
if (os.time() - gcdata["last"]) > groupChatTimeoutAfter then
groupchat_remove(botnick)
end
end
end
end
-- Explicitly remove a group chat. Only called by other groupchat_ helper
-- functions
function groupchat_remove(botnick)
if groupchats[botnick] then
-- Send leaving message to any still-connected users
groupchat_say(botnick, SZ_GCHAT_CLOSING)
-- Part the bot
Core.UnregBot(botnick)
local owner = groupchats[botnick]["owner"]
-- Remove from groupchat registry
groupchats[botnick] = nil
-- Refresh usercommands for the host
SC( Core.GetUser(owner), false )
end
end
-- Explicitly say a message into a group chat. Only called by other groupchat_
-- helper functions
function groupchat_say(botnick, message)
if groupchats[botnick] then
for destnick, _ in pairs(groupchats[botnick]["users"]) do
Core.SendPmToNick( destnick, botnick, message )
end
groupchats[botnick]["last"] = os.time()
end
end
-- Say a message from a user (i.e. don't echo it back to them)
function groupchat_sayas(botnick, selfnick, message)
if groupchats[botnick] then
local valid = false;
for destnick, _ in pairs(groupchats[botnick]["users"]) do
if destnick == selfnick then
valid = true
break
end
end
if valid then
for destnick, _ in pairs(groupchats[botnick]["users"]) do
if destnick ~= selfnick then
Core.SendPmToNick(destnick, botnick, "<"..selfnick.."> "..message)
end
end
else
Core.SendPmToNick(selfnick, botnick, SZ_GCHAT_AWAYMSG) -- pretend we're not here
end
end
end
-----------------------------------------------------------[ search parsing ]
function SearchArrival(user, data)
if bBlockIllegalSearches then
sstr=data
while string.find(sstr, "?") do
sstr=string.sub(sstr, string.find(sstr,"?")+1)
end
sstr="$"..string.lower( string.sub(sstr,1,string.len(sstr)-1) )
t = {}
for k in string.gmatch(sstr, "%$([^%$]+)") do
table.insert(t, k)
end
sstr=table.concat(t, " ")
if string.sub(sstr, 1, 4) ~= "tth:" then
local showbanner=false
for cnt=1,#badwords do
if string.find(sstr, badwords[cnt]) then
showbanner=true
break
end
end
if showbanner then
Core.SendToAll("<"..Core.GetHubSecAlias().."> "..user.sNick.." just searched for '"..sstr.."'!")
return true -- disable the search
end
end
end
end
-----------------------------------------------------------[ location ]
function GetHallFromIP(userNick)
if Core.GetUser(userNick) == nil then
return "* User "..userNick.." doesn't have a location."
else
-- Dumb check of third digit in IP
subnet = string.gsub(Core.GetUser(userNick).sIP, "%d+%D%d+%D(%d+)%D%d+", "%1")
subnet = math.floor(subnet / 8)
if hall[subnet]==nil then
return "* User "..userNick.." is located somewhere unknown"
else
return "* User "..userNick.." is located at "..hall[subnet]
end
end
end
-----------------------------------------------------------[ catchup ]
function SendCatchup(user)
local sz = getRange(ml, isz(mp), il)..getRange(ml, 0, isz(mp))
Core.SendToNick(user.sNick, "* Showing up to "..il.." messages.."..sz)
Core.SendToNick(user.sNick, "* Catchup completed.\n")
end
function isz(n) return (n + 1 + il) % il end
function logCatchup(text)
mp = isz(mp)
ml[mp] = os.date(tf).." "..text
end
function flushCatchup()
ml = {""}
mp = -1
end
-----------------------------------------------------------[ chatlogs ]
function logChat(text)
local logfile = os.date(chatLogPath)
local f = io.open(logfile, "a+")
f:write("["..os.date(chatLogTimeFormat).."] "..text.."\n")
f:close()
end
-----------------------------------------------------------[ general ]
function onError(sErrMsg)
Core.SendPmToOps(Core.GetHubSecAlias, "The hub script has broken down!\n"..sErrMsg)
end
function AddUSC(user, mode, menu, cstr)
Core.SendToUser(user, "$UserCommand "..mode.." "..menu.."$<%[mynick]> "..cstr.."&#124;")
end
-- @return nil|string
function readFile(path)
local f = io.open(path, "rb")
if (f) then
f:close()
lines = {}
for line in io.lines(path) do
lines[#lines + 1] = line
end
return table.concat(lines, "\n")
end
return nil
end
-- @return string
function getRange(array, first, last)
local c = first
local s = ""
while not (c >= last or array[c] == nil) do
s = s.."\n"..array[c]
c = c + 1
end
return s
end
function nmdc_escape(message)
local r = message:gsub("&", "&amp;"):gsub("|", "&#124;"):gsub("%$", "&#36;") -- escape $ sign
return r -- otherwise there are two arguments
end
function nmdc_unescape(message)
local r = message:gsub("&#36;", "$"):gsub("&#124;", "|"):gsub("&amp;", "&")
return r
end