--[[ FeedWatch 1.0f LUA 5.1x [Strict][API 2] By Mutor 03/28/08 A socket script that watches an RSS feed for changes, - Messages all online users with latest feed when update detected. - Saves to file for hub/script restarts. - Registers bot in order to pm users. - Option to select fields to display. - Option to replace certain strings. - Converts extended HTML ASCII characters. - Option to truncate field widths. Dependencies: PxLuaSocket 2.0.2, download here: http://www.czdc.org/PtokaX/Libs-0.4.0.0RC6/PXLuaSocket-2.0.2.7z Extract to PtokaX\scripts\libs +Changes from 1.0 04/27/08 +Changed return to only 'new feeds'. Requested by Trance +Added filter option for all HTML in field values +Added option to show new feeds in main chat or private message +Changes from 1.0b 05/01/08 +Added Append New Feed to Motd option. [Motd restored at script exit] Requested by Jusper +Broke out FeedNick to settings option. For P_pan +Changes from 1.0c 01/31/09 +Added Bot name may be preset or set as domain name, per feed +Added option for multiple feeds +Aded user table, users must enable feed messages +Added per user option for on/off/mainchat/pm +Changes from 1.0d 03/28/09 +Added ForceFeed option, Sends this Feed # to all in main chat. requested by P_pan +Changed to 1.0f by lUk3f1l3w4lK3R 04/11/10 - For the fact that i didnt find 1.0f for PtokaX i added the changes which Mutor added to the Eximius version of this script. +Added Assign a TAG to be used in lieu of address ]] --//-- -- "Botname" ["" = Use feed domain for Bot] local Bot = "[FeedWatch]" -- Admins nick for status / error messages local OpNick = "Mutor" -- "Command Menu" ["" = hub name] local Menu = "" -- "Command SubMenu" ["" = script name] local SubMenu = "" -- Assign a TAG to be used in lieu of address. Tag index should match index number in Feeds table above local Tags = { [1] = "eXs", } -- Set your feed's URL local Feeds = { [1] = {"http://exs.no-ip.org/feed/"}, } -- Always send this feed in main chat to all [0 = disable] local ForceFeed = 1 -- Start with which feed? local StartFeed = 2 -- Poll StartFeed at script start? true/false [false = poll at first timer interval] local PollAtStart = true -- Set the socket timeout value, in seconds local TimeOut = 5 -- Set the update interval [in minutes] local Refresh = 15 -- Maximum number of feeds to display local MaxFeeds = 25 -- Maximum number of feeds to cache to file local MaxCache = 25 -- File to save user data to local File = "FeedUsers.dat" -- Truncate RSS fields to this width. local MaxWidth = 100 -- Remove all HTML in feed fields? true/false local TagFilter = true -- Update Topic with new feed? [Topic restored at script exit] local DoTopic = false -- Enable capture of these RSS fields local Fields = { [""] = true, ["<description>"] = true, ["<link>"] = true, ["<author>"] = false, ["<category>"] = true, ["<comments>"] = false, ["<pubDate>"] = false, ["<guid>"] = false, } --Replace these strings in feed XML [--Comment unwanted replacements] local Rep = { ["%<%!%[CDATA%["] = "", ["%]%]%>"] = "", ["%[code *%]"] = "", ["<img [^>]*>"] = "", ["<p>"] = "", ["%s%s+"] = " ", ["\160+"] = " ", ["a href[^%w]+"] = "", ["\t"] = " ", ["\r\n"] = "", ["["] = string.char(91), ["]"] = string.char(93), [" [ ]+"] = " ", ["<summary type=\"html\">"] = "", ["</summary>"] = "", } --//-- local socket = require"socket" assert(socket,"Failed to load socket extension. Check files.") local http = require("socket.http") assert(http,"Failed to load http module. Check files.") local Path,Script = Core.GetPtokaXPath().."scripts/","FeedWatch 1.0f" local Tmr,Motd,Active = 0,"",0 OnStartup = function() MaxFeeds,Refresh,StartFeed = math.min(MaxFeeds,MaxCache),math.max(Refresh,2),math.min(StartFeed,#Feeds) Prefixes,TimeOut = SetMan.GetString(29),math.min(TimeOut,60) if Bot and #Bot > 0 and Bot ~= SetMan.GetString(21) then Bot = Bot:gsub("[%s%c]",string.char(160)) Core.RegBot(Bot,"RSS Feed Watch","",true) else Bot = False end if Menu == "" then Menu = SetMan.GetString(0) end if SubMenu == "" then SubMenu = Script end local str = SetMan.GetString(10) or "" if DoTopic then Topic,CurrTopic = str,str end for i = 1, #Feeds do local host = Tags[i] or Feeds[i][1]:gsub("^[hftp:]+[/]+",""):gsub("/.*$","") or "unavailable" table.insert(Feeds[i],Path..Feeds[i][1]:gsub("^[hftp:]+[/]+",""):gsub("[%c%p]","_")..".dat") if host and #host > 0 then table.insert(Feeds[i],host) end end for i,v in ipairs(Feeds) do if loadfile(v[2]) then dofile(v[2]) else local Old = {} SaveFile(v[2],Old,"Old") end if not Bot and v[3] then Core.RegBot(v[3],"RSS Feed Watch","",true) end end if not File:find("^"..Path,1,true) then File = Path..File end if loadfile(File) then dofile(File) else Users = {} SaveFile(File,Users,"Users") end Tmr = TmrMan.AddTimer(Refresh*60000) if PollAtStart then GetFeed(StartFeed) end end OnExit = function() if DoTopic and CurrTopic ~= "" then SetMan.SetString(10,CurrTopic) end if not Bot then for i,v in ipairs(Feeds) do Core.UnregBot(v[3]) end end end OnError = function(msg) local user,bot = Core.GetUser(OpNick),Bot or SetMan.GetString(21) if msg and user and bot then Core.SendToUser(user,"<"..bot.."> "..msg.."|") end end OnTimer = function(Id) if Id == Tmr then if StartFeed > #Feeds then StartFeed = 1 end GetFeed(StartFeed) StartFeed = StartFeed + 1 end end UserConnected = function(user) SendFwCmds(user) end OpConnected,RegConnected = UserConnected,UserConnected ChatArrival = function(user,data) local _,_,to = data:find("^$To: (%S+) From:") local _,_,cmd = data:find("%b<> ["..Prefixes.."](%a+)") if cmd and FwCmds[cmd] and FwCmds[cmd][2][user.iProfile] then local reply = FwCmds[cmd][1](user,data,Prefixes:sub(1,1)..cmd) if reply and reply:len() > 0 then if to and to == Bot or PmOnly then Core.SendPmToUser(user,Bot,reply.."|") else Core.SendToUser(user,"<"..Bot.."> "..reply.."|") end end return true end end ToArrival = ChatArrival SendFwCmds = function(user) local u,p,m,s,t,c = "$UserCommand 1 ",Prefixes:sub(1,1),Menu,SubMenu,{},"" for i,v in pairs(FwCmds) do if v[2][user.iProfile] then local bot = Bot or SetMan.GetString(21) local desc,arg1,arg2 = v[1]() table.insert(t,{desc,u.."1 "..m.."\\"..s.."\\"..desc.."$<%[mynick]> "..p..i..arg1.."||", u.."2 "..m.."\\"..s.."\\"..desc.."$$To: "..bot.." From: %[mynick] ".. "$<%[mynick]> "..p..i..arg2.."||"}) end end table.sort(t, function(a,b)return a[1] < b[1] end) for i,v in ipairs(t) do c = c..v[2]..v[3] end if c:len() > 0 then Core.SendToUser(user,c) Mem() return true end Mem() end ParseFeed = function(xml,n) New = {} local s = string.char(160) --if Bot then FeedName = Bot else FeedName = Feeds[n][3] or "unavailable" end for i,v in pairs(Rep) do xml = xml:gsub(i,v) end for item in xml:gmatch("<item>(.-)</item>") do item = item:gsub("[\r\n]",""):gsub("%<[%w ]+%/%>","%1 </>") local t = {} for field,val in item:gmatch("(%b<>)(.-)%<%/") do local s = "" if Fields[field:lower()] then field = field:lower():gsub("[<>]","")..":" if TagFilter then val = val:gsub("%b<>","") end val = val:gsub("^%W+","") s = s.." "..string.format("%-20.13s",field).."\t" if val:len() > MaxWidth then s = s..val:sub(1,MaxWidth).."..." else s = s..val.."" end end if s:len() > 0 then table.insert(t,s) end end if next(t) then table.insert(New,t) end end if next(New) then local reply,cnt = "",0 while #New > MaxCache do table.remove(New) end if loadfile(Feeds[n][2]) then dofile(Feeds[n][2]) end for key,val in ipairs(New) do local bool = true if Old and next(Old) then for i,v in ipairs(Old) do if v[2] == val[2] then bool = false break end end end if bool then cnt = cnt + 1 if cnt <= MaxFeeds then for i,v in ipairs(val) do reply = reply.."\t"..v.."\n" end reply = reply.."\n" end end end if reply:len() > 0 then local plural = "" if cnt > 1 then plural = "s" end SaveFile(Feeds[n][2],New,"Old") if Old then Old = nil end if DoTopic then Topic = New[1][1]:gsub(" title:",""):gsub(" [ \t]+"," ") if Topic and #Topic > 0 then Topic = "Update From: "..Feeds[n][3].." "..Topic if SetMan.GetString(10) ~= Topic then SetMan.SetString(10,Topic) end end end local s = "\n\n\t[ "..tostring(cnt).." ] New feed"..plural.. " from: "..Feeds[n][3].." ...\n\n"..reply.."\n" return s end if Old then Old = nil end else if DoTopic and CurrTopic ~= "" and SetMan.GetString(10) ~= CurrTopic then SetMan.SetString(10,CurrTopic) end end end GetFeed = function(n) local st = socket.gettime() n = math.min(n,#Feeds) http.TIMEOUT = TimeOut local s,fd,sz,hd = "",http.request(Feeds[n][1]) if fd and sz then local td,plural = socket.gettime()-st,"of a second." if td > 1 then plural = "seconds." end local time = string.format("%.2f "..plural,td) local msg = ParseFeed(Decode(fd),n) local Send = function(bot,str) for i,v in ipairs(Users) do local user = Core.GetUser(v[1]) if user and v[3] then if v[2] == "p" then Core.SendPmToUser(user,bot,str.."|") elseif v[2] == "m" then Core.SendToUser(user,"<"..bot.."> "..str.."|") end end end end if msg and msg:len() > 0 then local bot = Bot or Feeds[n][3] if ForceFeed > 0 and n == ForceFeed then Core.SendToAll("<"..bot.."> "..msg.."\t\t\tProcessed In: "..time.."\n\n".."|") else Send(bot,msg.."\t\t\tProcessed In: "..time.."\n\n") end end if FeedName then Core.UnregBot(FeedName) FeedName = nil end Mem() end end Decode = function(data,rev) local Uni = { ["–"] = "–",["—"] = "—",["‘"] = "‘",["’"] = "’",["‚"] = "‚", ["“"] = "“",["”"] = "”",["„"] = "„",["†"] = "†",["‡"] = "‡", ["•"] = "•",["…"] = "…",["‰"] = "‰",["‹"] = "‹",["›"] = "›", } local Asc = { [34]=""",[38]="&",[39]="'",[60]="<",[62]=">",[94]="ˆ",[126]="˜",[127]="", [128]="€",[130]="‚",[131]="ƒ",[132]="„",[133]="…",[134]="†",[135]="‡", [136]="ˆ",[137]="‰",[138]="Š",[139]="‹",[140]="Œ",[142]="Ž",[145]="‘", [146]="’",[147]="“",[148]="”",[149]="•",[150]="–",[151]="—",[152]="˜", [153]="™",[154]="š",[155]="›",[156]="œ",[157]="Ť",[158]="ž",[159]="Ÿ", [160]=" ",[161]="ˇ",[162]="˘",[163]="Ł",[164]="¤",[165]="Ą",[166]="¦", [167]="§",[168]="¨",[169]="©",[170]="Ş",[171]="«",[172]="¬",[173]="­", [174]="®",[175]="Ż",[176]="°",[177]="±",[178]="²",[179]="ł",[180]="´", [181]="µ",[182]="¶",[183]="·",[184]="¸",[185]="ą",[186]="ş",[187]="»", [188]="Ľ",[189]="˝",[190]="ľ",[191]="ż",[192]="Ŕ",[193]="Á",[194]="Â", [195]="Ă",[196]="Ä",[197]="Ĺ",[198]="Ć",[199]="Ç",[200]="Č",[201]="É", [202]="Ę",[203]="Ë",[204]="Ě",[205]="Í",[206]="Î",[207]="Ď",[208]="Đ", [209]="Ń",[210]="Ň",[211]="Ó",[212]="Ô",[213]="Ő",[214]="Ö",[215]="×", [216]="Ř",[217]="Ů",[218]="Ú",[219]="Ű",[220]="Ü",[221]="Ý",[222]="Ţ", [223]="ß",[224]="à",[225]="á",[226]="â",[227]="ă",[228]="ä",[229]="ĺ", [230]="ć",[231]="ç",[232]="č",[233]="é",[234]="ę",[235]="ë",[236]="ě", [237]="í",[238]="î",[239]="ď",[240]="đ",[241]="ń",[242]="ň",[243]="ó", [244]="ô",[245]="ő",[246]="ö",[247]="÷",[248]="ř",[249]="ů",[250]="ú", [251]="ű",[252]="ü",[253]="ý",[254]="ţ",[255]="˙", } for i,v in pairs(Uni) do if rev then data = data:gsub(v,i) else data = data:gsub(i,v) end end for i,v in pairs(Asc) do local c = string.char(i) if rev then data = data:gsub(c,v) else data = data:gsub(tostring(v),c) end end return data end ChkUsers = function(n) for i,v in ipairs(Users) do if n:lower() == v[1]:lower() then return i end end end FwCmds = { feeds = {function(user,data,cmd) if user then local _,_,choice = data:lower():find("%b<> "..cmd.." ([pmonf^|]+)|") if choice then local t = {["on"] = true,["off"] = {true,false},["m"] = "main chat",["p"] = "private message"} if t[choice] then local b,bool = ChkUsers(user.sNick) local tab = {["true"] = "enabled",["false"] = "disabled"} if not b then table.insert(Users,{user.sNick,"p",true}) if not bool then bool = true end b = #Users else if choice == "on" then if Users[b][3] then return user.sNick..", feeds are already enabled for ".. "you and will be sent in "..t[Users[b][2]] else Users[b][3] = t[choice] if not bool then bool = true end end elseif choice == "off" then if not Users[b][3] then return user.sNick..", feeds are already disabled for ".. "you and will be sent in "..t[Users[b][2]].." when enabled." else Users[b][3] = t[choice][2] if not bool then bool = true end end else if Users[b][2] == choice then return user.sNick..", feeds are already set for ".. t[choice].." and are curretly "..tab[tostring(Users[b][3])] else Users[b][2] = choice if not bool then bool = true end end end end if bool then SaveFile(File,Users,"Users") end return "Feeds are currently "..tab[tostring(Users[b][3])].. " current message type: "..t[Users[b][2]] else return "**Error in selection. Usage: "..cmd.." <on/off/p/m>" end else return "Error in selection. Usage: "..cmd.." <on/off/p/m>" end else return "Set your RSS feed option", " %[line:on=enabled, off= disabled, m=main, p=pm]", " %[line:on=enabled, off= disabled, m=main, p=pm]" end end, {[-1] = false,[0] = true,[1] = true,[2] = true,[3] = true} }, listusers = {function(user,data,cmd) if user then local r = "¯" local reply,t,c = "\n\n\t"..Script.." Active Users\n\n\t"..r:rep(50).."\r\n".. "\tNickname\t\tMessage Type\tStatus\r\n\t"..r:rep(50).."\r\n",{},"" local tab = {["true"] = "enabled",["false"] = "disabled", ["m"] = "main chat ",["p"] = "private message"} for i,v in ipairs(Users) do table.insert(t,"\t"..string.format("%-30s",v[1]).."\t".. tab[v[2]].."\t"..tab[tostring(v[3])].."\r\n") end table.sort(t, function(a,b)return a < b end) c = table.concat(t,"") if c:len() > 0 then return reply..c.."\n\t"..r:rep(50).."\r\n\r\n" end else return "List Active Feed Users","","" end end, {[-1] = false,[0] = true,[1] = true,[2] = true,[3] = false} }, feedhelp = {function(user,data,cmd) if user then local reply,t,c = "\n\n\t"..Script.." Command Help\n\n\tCommand".. "\t\tDescription\r\n\t"..string.rep("¯",40).."\r\n",{},"" for i,v in pairs(FwCmds) do local desc,args = FwCmds[i][1]() table.insert(t,"\t+"..string.format("%-15s",i).."\t"..desc.."\r\n") end table.sort(t, function(a,b)return a < b end) for i,v in ipairs(t) do c = c..v end if c:len() > 0 then return reply..c.."\n\t"..string.rep("¯",40).."\r\n\r\n" end else return Script.." Help","","" end end, {[-1] = false,[0] = true,[1] = true,[2] = true,[3] = true} } } Mem = function() collectgarbage("collect") return string.format("%-.2f Kb.",collectgarbage("count")) end SaveFile = function(file,table, tablename ) local hFile = io.open (file , "w") Serialize(table, tablename, hFile) hFile:close() end Serialize = function(tTable, sTableName, hFile, sTab) sTab = sTab or ""; hFile:write(sTab..sTableName.." = {\n" ) for key, value in ipairs(tTable) do local sKey = (type(key) == "string") and string.format("[%q]",key) or string.format("[%d]",key) if(type(value) == "table") then Serialize(value, sKey, hFile, sTab.."\t") else local sValue = (type(value) == "string") and string.format("%q",value) or tostring(value) hFile:write( sTab.."\t"..sKey.." = "..sValue) end hFile:write( ",\n") end hFile:write( sTab.."}") end