local tCfg = {
-- ник бота, оставьте "" для использования имени бота хаба
sBot = "",
-- отправлять меню при входе в хаб, 1 или 0
bMenuOnEntry = 1,
-- сохранять +me сообщения, 1 или 0
bLogMeCmds = 1,
-- укорачивать сообщения в истории чата до этого количества символов
-- оставьте 0, если укорачивать не требуется
iMaxLen = 0,
-- максимальное количество сохраняемых в истории чата сообщений
iMaxMsgs = 100,
-- количество сообщений при входе в хаб
iMsgsOnEntry = 12,
-- корректировка времени, секунд
iTimeOffset = 0,
-- интервал автосохранения истории, минут
iAutoSave = 1,
-- формат времени написания сообщений в истории чата
sTimeStamp = "[%H:%M:%S] ",
-- относительный путь к файлу сохранения истории
sFile = "Chat.dat",
iMenuDefaultContext = 3,
}
local tCmdsConf = { -- настройки команд: название команды; профили с доступом к команде;
-- использовать ли указанные профили при формировании пула профилей-получателей отчётов;
-- создавать меню для команды; показывать команду в справке; очерёдность в меню и в справке;
-- правила создания меню (контекст, родительское меню, название строки с переводом дополнительных параметров,
-- сырые дополнительные параметры меню, разделитель над меню); параметры команды для справки
tScriptHelp = { "histhelp", {0,1,2,3,4,-1}, 0, 1, 1, 90,
{3,"","","",1} },
tGlobalHelp = { "help", {0,1,2,3,4,-1}, 0, 0, 0, 99 },
tGetChatHistory = { "history", {0,1,2,3,4,-1}, 0, 1, 1, 10,
{3, "", "sMenuShowParams"}, " <count>"},
tDelMsgByString = { "chdelstr", {0,1,2}, 1, 1, 1, 20,
{3, "", "sMenuDMBSParams"}, " <text>"},
tDelMsgFromNick = { "chdelnick", {0,1,2}, 1, 1, 1, 30,
{ {3,"","sMenuDMBNParams"}, {2,"",""," %[nick]"}, }, " <nick>"},
tDelMsg = { "chdelmsg", {0,1,2}, 1, 1, 1, 40,
{3, "", "sMenuDMParams"}, " <number>[ <number>]"},
tDelChatHistory = { "chdelete", {0,1}, 1, 1, 1, 50,
{3, "", "sMenuDCHParams"} },
}
local tExceptions = { -- шаблоны, сообщения с которыми обрезаться не будут
"magnet:%?xt=urn:tree:tiger:[a-zA-Z%d]+&xl=%-?%d+&dn=%S+", -- магнет-ссылки
"https?://%S+", -- веб-ссылки
"www%.%S+", -- веб-ссылки
"ftp[:.]%S+", -- фтп-ссылки
"dchub://%S+", -- хаб-ссылки
}
-- соответствие страны пользователя языку получаемых им сообщений от скрипта
local tLangs = {
Status = "Russian", -- Рассылка операторам
}
local tMsgs = {}
tMsgs["Russian"] = {
sBadBotName = "Имя бота содержит запрещённые символы ($ или | или пробел). Они были заменены на их коды.",
sCHEmpty = "История чата пуста.",
sCHLong = "Последние [COUNT] сообщений в чате:\n [MSGS]\n",
sCHShort = "Предыдущие сообщения в чате:",
sCHCleared = "%s очистил историю чата.",
sDelMsg = "%s удалил из истории чата %s сообщений: %s",
sDelMsgNick = "%s удалил из истории чата %s сообщений от ника '%s'.",
sDelMsgPat = "%s удалил из истории чата %s сообщений, содержащих строку '%s'.",
sHelpHeader = "Вам доступны следующие команды:\n",
sMsgNotFound = "%s\n\tСообщение #%s не найдено.",
sNickNotFound = "В истории чата не найдено сообщений от ника '%s'.",
sNoArg = "Не найден необходимый параметр команды.",
sFileError = "Не удалось открыть файл",
sPatternNotFound = "В истории чата не найдено сообщений, содержащих строку '%s'.",
sRegBotFail = "Не удалось зарегистрировать бота '%s'.",
sSubMenu = "История чата",
sMenuDCHParams = "Вы действительно хотите удалить все сообщения в истории чата?",
sMenuDMBNParams = "Введите ник",
sMenuDMBSParams = "Введите образец строки для удаления",
sMenuDMParams = "Введите номера удаляемых сообщений (через пробел)",
sMenuShowParams = "Сколько сообщений показать?",
sHelpDelChatHistory = "очистить историю чата",
sHelpDelMsg = "удалить сообщения с указанными номерами (нумерация от новейшего сообщения)",
sHelpDelMsgByString = "удалить сообщения, содержащие указанный образец текста",
sHelpDelMsgFromNick = "удалить сообщения от указанного ника",
sHelpGetChatHistory = "показать последние <count> сообщений в истории чата (без параметра - все сообщения)",
sHelpScriptHelp = "эта справка по командам",
sMenuDelChatHistory = "Очистить",
sMenuDelMsg = "Удалить по номеру",
sMenuDelMsgByString = "Удалить по образцу",
sMenuDelMsgFromNick = "Удалить сообщения ника",
sMenuDelMsgFromNick2 = "Удалить сообщения этого юзера",
sMenuGetChatHistory = "Показать",
sMenuScriptHelp = "Справка",
}
------------------------------------------------------------
local tCmds, tChat, sChatHistory
local tUC, tHelp = {}, {}
local bToSave, bChanges = true, true
function OnError(sErrMsg)
Core.SendToOps("<"..Core.GetHubSecAlias().."> Error "..sErrMsg)
return true
end
function OnStartup()
tCfg.sBot = tCfg.sBot and tCfg.sBot ~= "" and tCfg.sBot
or SetMan.GetString( SetMan.tStrings and SetMan.tStrings.HubBotNick or 21 )
for i,v in pairs(tCfg) do
if i:byte(1) == 98 then -- b
tCfg[i] = v == 1
end
end
tCfg.bTruncateMsgs = tCfg.iMaxLen ~= 0
local sPrefixes = SetMan.GetString( SetMan.tStrings and SetMan.tStrings.ChatCommandsPrefixes or 29 )
if not sPrefixes or #sPrefixes == 0 then sPrefixes = "!" end
local sPrefix = sPrefixes:sub(1,1)
sPrefixes = sPrefixes:gsub("[%^%%%.%[%]%-]","%%%1")
tCfg.sMcPattern = "^<[^ |$]+> %s*(["..sPrefixes.."]%S+)"
tCfg.sPmPattern = "^$To:%s+([^ ]+)%s+From:%s+[^ ]+%s+$<[^ |$]+>( %s*["..sPrefixes.."](%S+).*)|$"
local sUC = "$UserCommand 1 [CONTEXT] [MENU]$<%[mynick]> [COMMAND][PARAMS]||"
if not tLangs.Status then tLangs.Status = next(tMsgs) end
if not tLangs.Other then tLangs.Other = tLangs.Status end
if not tLangs.NoIP2C then tLangs.NoIP2C = tLangs.Status end
local t = {}
for i,v in pairs(tLangs) do
if not tMsgs[v] then
table.insert(t, i)
end
end
for i,v in ipairs(t) do tLangs[v] = nil end
for sLangName, tLangMsgs in pairs(tMsgs) do
t = {}
for i,v in pairs(tLangMsgs) do
if (i:sub(1,5) == "sMenu" or i:sub(1,5) == "sHelp") and #v == 0 then
table.insert(t, i)
end
end
for i,v in ipairs(t) do tLangMsgs[v] = nil end
end
local tTempStrings, tTempUC, tTempHelp, tReportToProfs = {}, {}, {}, {}
for k,tComConf in pairs(tCmdsConf) do
tTempStrings[k] = {}
for sLangName, tLangMsgs in pairs(tMsgs) do
tTempStrings[k][sLangName] = {}
local tTempStringsLang = tTempStrings[k][sLangName]
if tComConf[4] == 1 then
if tComConf[7] and tComConf[7][1] and type(tComConf[7][1]) == 'table' then
for i,v in ipairs(tComConf[7]) do
local sSubMenu = tLangMsgs['sSubMenu'..(v[2] or "")]
if v[5] then
tTempStringsLang[1] = (tTempStringsLang[1] or "").."$UserCommand 0 "..(v[1] or tCfg.iMenuDefaultContext).." |"
end
tTempStringsLang[1] = (tTempStringsLang[1] or "")..(sUC:gsub("%[([A-Z]+)%]", {
CONTEXT = v[1] or tCfg.iMenuDefaultContext,
MENU = (sSubMenu and #sSubMenu ~= 0 and sSubMenu.."\\" or "")
..(tLangMsgs["sMenu"..k:sub(2)..i] or tLangMsgs["sMenu"..k:sub(2)] or k:sub(2)),
COMMAND = sPrefix..tComConf[1],
PARAMS =
(v[3] and #v[3] ~= 0 and (" %%[line:%s]"):format(tLangMsgs[ v[3] ]) or "")
..(v[4] and #v[4] ~= 0 and v[4] or ""),
}))
end
else
local sSubMenu = tLangMsgs['sSubMenu'..(tComConf[7] and tComConf[7][2] or "")]
if tComConf[7] and tComConf[7][5] then
tTempStringsLang[1] = "$UserCommand 0 "..(tComConf[7][1] or tCfg.iMenuDefaultContext).." |"
end
tTempStringsLang[1] = (tTempStringsLang[1] or "")..(sUC:gsub("%[([A-Z]+)%]", {
CONTEXT = tComConf[7] and tComConf[7][1] or tCfg.iMenuDefaultContext,
MENU = (sSubMenu and #sSubMenu ~= 0 and sSubMenu.."\\" or "")
..(tLangMsgs["sMenu"..k:sub(2)] or k:sub(2)),
COMMAND = sPrefix..tComConf[1],
PARAMS = (
tComConf[7] and tComConf[7][3] and #tComConf[7][3] ~= 0
and (" %%[line:%s]"):format(tLangMsgs[ tComConf[7][3] ]) or ""
) .. (
tComConf[7] and tComConf[7][4] and #tComConf[7][4] ~= 0
and tComConf[7][4] or ""
),
}))
end
else
tTempStringsLang[1] = ""
end
table.insert(tTempStringsLang, tComConf[5] == 1 and ("\n\t%s%s%s - %s."):format(
sPrefix, tComConf[1], tComConf[8] or "", tLangMsgs["sHelp"..k:sub(2)] or "not described yet") or "")
end
local tPerms = {}
for _,iProf in ipairs(tComConf[2]) do
tPerms[iProf] = true
if tComConf[3] == 1 then
tReportToProfs[iProf] = true
end
if tComConf[4] == 1 then
if not tTempUC[iProf] then tTempUC[iProf] = {} end
table.insert(tTempUC[iProf], k)
end
if tComConf[5] == 1 then
if not tTempHelp[iProf] then tTempHelp[iProf] = {} end
table.insert(tTempHelp[iProf], k)
end
end
if tCmds[tComConf[1]] then
table.insert(tCmds[tComConf[1]], tPerms)
end
end
for iProf,tComs in pairs(tTempUC) do
table.sort(tComs, function(a,b) return tCmdsConf[a][6] < tCmdsConf[b][6] end)
for _,sCmdsConfIdx in ipairs(tComs) do
for sLangName, tLangUC in pairs(tTempStrings[sCmdsConfIdx]) do
if not tUC[sLangName] then tUC[sLangName] = {} end
tUC[sLangName][iProf] = (tUC[sLangName][iProf] or "")..tLangUC[1]
end
end
end
for iProf,tComs in pairs(tTempHelp) do
table.sort(tComs, function(a,b) return tCmdsConf[a][6] < tCmdsConf[b][6] end)
for _,sCmdsConfIdx in ipairs(tComs) do
for sLangName, tLangHelp in pairs(tTempStrings[sCmdsConfIdx]) do
if not tHelp[sLangName] then tHelp[sLangName] = {} end
tHelp[sLangName][iProf] = (tHelp[sLangName][iProf] or "")..tLangHelp[2]
end
end
end
local tReportTo = {}
for i in pairs(tReportToProfs) do
table.insert(tReportTo, i)
end
if #tReportTo == 0 then table.insert(tReportTo, 0) end
Report = function(sMsg)
if SetMan.GetBool( SetMan.tBooleans and SetMan.tBooleans.SendStatusMessagesAsPm or 30 ) then
local sMsg = "*** "..tostring(sMsg)
for i,v in ipairs(tReportTo) do
Core.SendPmToProfile(v, tCfg.sBot, sMsg)
end
else
local sMsg = "<"..tCfg.sBot.."> *** "..tostring(sMsg)
for i,v in ipairs(tReportTo) do
Core.SendToProfile(v, sMsg)
end
end
end
local bBotExists
if tCfg.sBot == SetMan.GetString( SetMan.tStrings and SetMan.tStrings.HubBotNick or 21 ) then
bBotExists = true
else
for i,v in ipairs(Core.GetBots()) do
if v.sName == tCfg.sBot then
bBotExists = true
break
end
end
end
if not bBotExists then
if tCfg.sBot:find"[|$ ]" then
tCfg.sBot = tCfg.sBot:gsub("[|$ ]", function(s) return('\\'..string.byte(s)) end)
Report(tMsgs[tLangs.Status].sBadBotName)
end
if not Core.RegBot(tCfg.sBot,"","",true) then
Report(tMsgs[tLangs.Status].sRegBotFail:format(tCfg.sBot))
end
end
tCfg.sFile = Core.GetPtokaXPath().."scripts/"..tCfg.sFile
local Ret = loadfile(tCfg.sFile)
if Ret then Ret() end
tChat = _G.tChat or {}
while #tChat > tCfg.iMaxMsgs do
table.remove(tChat, 1)
end
tCfg.iAutoSave = tCfg.iAutoSave * 60 * 1000
TmrMan.AddTimer(tCfg.iAutoSave, 'OnExit')
OnExit()
end
function Serialize(tTable, sTableName, hFile, sTab)
sTab = sTab or ''
hFile:write(sTab..sTableName.." = {\n")
for k, v in pairs(tTable) do
if type(v) ~= "function" then
local sKey = type(k) == "string" and ("[%q]"):format(k) or ("[%d]"):format(k)
if type(v) == "table" then
Serialize(v, sKey, hFile, sTab..'\t')
else
local sValue = type(v) == "string" and ("%q"):format(v) or tostring(v)
hFile:write(sTab..'\t'..sKey.." = "..sValue)
end
hFile:write(",\n")
end
end
hFile:write(sTab.."}")
end
function SaveTable(sFile, tTable, sTableName)
local hFile, sErr = io.open(sFile, "w+")
if hFile then
Serialize(tTable, sTableName, hFile)
hFile:flush()
hFile:close()
return true
end
error( ("%s %s\n%s"):format(tMsgs[tLangs.Status].sFileError, sFile, sErr), 0 )
end
function OnExit()
if bToSave then
bToSave = false
SaveTable(tCfg.sFile, tChat, "tChat")
end
end
function ChatArrival(tUser, sData)
sData = sData:sub(1,-2)
local sCmdOrNick = sData:match(tCfg.sMcPattern)
if sCmdOrNick then
local sCmd = sCmdOrNick:sub(2):lower()
if tCmds[sCmd] and tCmds[sCmd][2][tUser.iProfile] then
-- local sLang = tLangs[IP2Country.GetCountryCode(tUser.sIP) or "NoIP2C"] or tLangs.Other
local sLang = tLangs[Core.GetUserValue(tUser, 26) or "NoIP2C"] or tLangs.Other
local bRet, sResult = tCmds[sCmd][1](tUser, sLang, sData:sub(#tUser.sNick + 3))
if sResult and type(sResult) == "string" then
Core.SendToUser(tUser, "<"..tCfg.sBot.."> "..sResult)
end
return bRet
elseif Core.GetUser(sCmdOrNick) or Core.GetUser(sCmdOrNick:sub(1,-2)) then
sCmdOrNick = nil
else
sCmdOrNick = sCmdOrNick:match("^.(%a%w+)$")
end
end
if not sData:find"is kicking" and (not sCmdOrNick or (tCfg.bLogMeCmds and sCmdOrNick == "me")) then
if sCmdOrNick then
sData = "* "..tUser.sNick..sData:sub(#tUser.sNick + 7)
end
if tCfg.bTruncateMsgs then
local bTrimMsg = true
local iLen = #sData - #tUser.sNick - 3
for i,v in ipairs(tExceptions) do
if sData:find(v) then
bTrimMsg = false
break
end
end
if bTrimMsg and iLen > tCfg.iMaxLen then
sData = sData:sub(1, tCfg.iMaxLen - iLen - 1).."[…]"
end
end
if not bToSave then bToSave = true end
bChanges = true
table.insert(tChat, os.date(tCfg.sTimeStamp, os.time() + tCfg.iTimeOffset)..sData)
while #tChat > tCfg.iMaxMsgs do
table.remove(tChat, 1)
end
end
end
function ToArrival(tUser, sData)
local sTo, sMsg, sCmd = sData:match(tCfg.sPmPattern)
if sTo and sTo == tCfg.sBot and tCmds[sCmd:lower()] and tCmds[sCmd:lower()][2][tUser.iProfile] then
-- local sLang = tLangs[IP2Country.GetCountryCode(tUser.sIP) or "NoIP2C"] or tLangs.Other
local sLang = tLangs[Core.GetUserValue(tUser, 26) or "NoIP2C"] or tLangs.Other
local bRet, sResult = tCmds[sCmd:lower()][1](tUser, sLang, sMsg, true)
if sResult and type(sResult) == "string" then
Core.SendPmToUser(tUser, tCfg.sBot, sResult)
end
return bRet
end
end
function UserConnected(tUser)
-- local sLang = tLangs[IP2Country.GetCountryCode(tUser.sIP) or "NoIP2C"] or tLangs.Other
local sLang = tLangs[Core.GetUserValue(tUser, 26) or "NoIP2C"] or tLangs.Other
if tCfg.bMenuOnEntry and Core.GetUserValue(tUser, 12) and tUC[sLang] and tUC[sLang][tUser.iProfile] then
Core.SendToUser(tUser, tUC[sLang][tUser.iProfile])
end
if bChanges then
if #tChat ~= 0 then
bChanges = false
sChatHistory = ("\n\173%s\n"):format(table.concat(
tChat, "\n\173", #tChat - math.min(tCfg.iMsgsOnEntry, #tChat) + 1))
else
Core.SendToUser(tUser, "<"..tCfg.sBot.."> "..tMsgs[sLang].sCHEmpty)
return
end
end
Core.SendToUser(tUser, ("<%s> %s %s"):format(tCfg.sBot, tMsgs[sLang].sCHShort, sChatHistory) )
end
RegConnected, OpConnected = UserConnected, UserConnected
tCmds = {
[tCmdsConf.tScriptHelp[1]] = {function(tUser, sLang)
return true, tMsgs[sLang].sHelpHeader..tHelp[sLang][tUser.iProfile]
end},
[tCmdsConf.tGlobalHelp[1]] = {function(tUser, sLang, _, bPM)
if not bPM and SetMan.GetBool( SetMan.tBooleans and SetMan.tBooleans.ReplyToHubCommandsAsPm or 36 ) then
Core.SendPmToUser(tUser, tCfg.sBot, tMsgs[sLang].sHelpHeader..tHelp[sLang][tUser.iProfile])
return false
end
return false, tMsgs[sLang].sHelpHeader..tHelp[sLang][tUser.iProfile]
end},
[tCmdsConf.tGetChatHistory[1]] = {function(tUser, sLang, sData)
if #tChat == 0 then
return true, tMsgs[sLang].sCHEmpty
end
local iMsgs = sData:match"^%s+[^ ]+%s+(%d+)"
iMsgs = math.min(tonumber(iMsgs) or #tChat, #tChat)
return true, tMsgs[sLang].sCHLong:gsub("%[([A-Z]+)%]", {
COUNT = iMsgs,
MSGS = table.concat(tChat, "\n ", #tChat - iMsgs + 1),
})
end},
[tCmdsConf.tDelChatHistory[1]] = {function(tUser)
tChat = {}
bToSave = true
bChanges = true
Report(tMsgs[tLangs.Status].sCHCleared:format(tUser.sNick))
return true
end},
[tCmdsConf.tDelMsgByString[1]] = {function(tUser, sLang, sData)
local sPattern = sData:match"^%s+[^ ]+%s+(.*)"
if not sPattern or sPattern == "" then
return true, tMsgs[sLang].sNoArg
end
local t = {}
for i,v in ipairs(tChat) do
if v:find(sPattern,1,true) then
table.insert(t,i)
end
end
if #t == 0 then
return true, tMsgs[sLang].sPatternNotFound:format(sPattern)
end
table.sort(t)
for i=#t,1,-1 do
table.remove(tChat, t[i])
end
bToSave = true
bChanges = true
Report(tMsgs[tLangs.Status].sDelMsgPat:format(tUser.sNick, #t, sPattern))
return true
end},
[tCmdsConf.tDelMsgFromNick[1]] = {function(tUser, sLang, sData)
local sNick = sData:match"^%s+[^ ]+%s+([^ ]+)"
if not sNick then
return true, tMsgs[sLang].sNoArg
end
local sTS = (tCfg.sTimeStamp:gsub("[%^%$%(%)%%%.%[%]%*%+%-%?]","%%%1")):gsub("%%%%[a-zA-Z]","%[%%da-zA-Z%]+")
local sNickP = sNick:gsub("[%^%$%(%)%%%.%[%]%*%+%-%?]","%%%1")
local sPattern1 = "^"..sTS.."<" ..sNickP..">"
local sPattern2 = "^"..sTS.."%* "..sNickP
local t = {}
for i,v in ipairs(tChat) do
if v:find(sPattern1) or v:find(sPattern2) then
table.insert(t,i)
end
end
if #t == 0 then
return true, tMsgs[sLang].sNickNotFound:format(sNick)
end
table.sort(t)
for i=#t,1,-1 do
table.remove(tChat, t[i])
end
bToSave = true
bChanges = true
Report(tMsgs[tLangs.Status].sDelMsgNick:format(tUser.sNick, #t, sNick))
return true
end},
[tCmdsConf.tDelMsg[1]] = {function(tUser, sLang, sData)
local t = {}
for n in sData:gmatch" (%d+)" do
table.insert(t, tonumber(n))
end
if #t == 0 then
return true, tMsgs[sLang].sNoArg
end
local iChat, iCounter, sRet = #tChat, 0, ""
table.sort(t)
for i=1,#t do
local sMsg = table.remove(tChat, iChat - t[i] + 1)
if sMsg then
iCounter = iCounter + 1
sRet = ("%s\n\t%s: %s"):format(sRet, t[i], sMsg)
else
sRet = tMsgs[sLang].sMsgNotFound:format(sRet, t[i])
end
end
if iCounter == 0 then
return true, sRet
end
bToSave = true
bChanges = true
Report(tMsgs[tLangs.Status].sDelMsg:format(tUser.sNick, iCounter, sRet))
return true
end},
}