-- ************************************************************************ -- ** Copyright 2012 mniip -- ** mniip@yandex.ru -- ************************************************************************ -- ** This program is free software: you can redistribute it and/or modify -- ** it under the terms of the GNU General Public License as published by -- ** the Free Software Foundation, either version 3 of the License, or -- ** (at your option) any later version. -- ** -- ** This program is distributed in the hope that it will be useful, -- ** but WITHOUT ANY WARRANTY; without even the implied warranty of -- ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- ** GNU General Public License for more details. -- ** -- ** You should have received a copy of the GNU General Public License -- ** along with this program. If not, see <http://www.gnu.org/licenses/>. -- ************************************************************************ version="xsTPTIRC v1.1 beta by mniip" local n=tpt.get_name() if not n or n=="" then error"you MUST be logged in to use xsTPTIrc" end local mynick=n local conf={} local function loadpresets() local f=io.open"irc.conf" if not f then f=io.open("irc.conf","w") f:write('nick="'..mynick..[[" server="irc.freenode.net" port=6667 autojoin={"#powder-social"} wcolor={255,255,0} tabtextcolor={255,255,255} ctabcolor={255,0,255,63} nickcolor={0,255,255} textcolor={127,255,255} opcolor={255,0,0} voicecolor={64,192,64} mycolor={255,255,255} mecolor={128,128,128} chancolor={255,127,0} ]]) f:close() f=io.open"irc.conf" end local s=f:read"*a" f:close() f=loadstring(s) if f then setfenv(f,conf) f() end mynick=tostring(conf.nick) or n conf.server=tostring(conf.server) or "irc.freenode.net" conf.port=tonumber(conf.port) or 6667 conf.autojoin=type(conf.autojoin)=="table" and conf.autojoin or {"#powder"} end loadpresets() pcall(package.loadlib,"socket","luasocket.dll") local loader=package.loadlib("luasocket.dll","luaopen_socket_core") if loader then loader() else socket=require("socket") end local drawtext=function(x,y,s,...)return pcall(tpt.drawtext,x,y,tostring(s):gsub("\15(.)(.)(.)", function(r,g,b)return "\15"..(r=="\0"and"\1"or r)..(g=="\0"and"\1"or g)..(b=="\0"and"\1"or b) end),...)end local function textwidth(s) return select(2,pcall(tpt.textwidth,s)) end local function r(t) return "\15"..string.char(unpack(t)) end local tabs local ctab=1 local edit="" local cpos=0 local cursorbl=0 local history={[0]=""} local histp=0 local wx,wy=10,10 local wcolor=conf.wcolor or {255,255,0} local tabtextcolor=conf.tabtextcolor or {255,255,255} local ctabcolor=conf.ctabcolor or {255,0,255,63} local nickcolor=conf.nickcolor or {0,255,255} local textcolor=conf.textcolor or {127,255,255} local opcolor=conf.opcolor or {255,0,0} local voicecolor=conf.voicecolor or {64,192,64} local mycolor=conf.mycolor or {255,255,255} local mecolor=conf.mecolor or {128,128,128} local chancolor=conf.chancolor or {255,127,0} local function ui() tpt.fillrect(wx,wy,600,360,0,0,0,240) -- Window tpt.drawrect(wx,wy,600,360,unpack(wcolor)) -- Topic tpt.drawrect(wx,wy+12,484,12,unpack(wcolor)) local f=#tabs[ctab].topic while textwidth(tabs[ctab].topic:sub(1,f))>480 do f=f-1 end drawtext(wx+2,wy+14,tabs[ctab].topic:sub(1,f),unpack(wcolor)) tpt.drawrect(wx+484,wy+12,12,12,unpack(wcolor)) -- Tabs if ctab>#tabs then ctab=#tabs end local ox=0 for i=1,#tabs do tpt.drawrect(wx+ox,wy,#tabs[i].name*6+2,12,unpack(wcolor)) drawtext(wx+ox+2,wy+2,tabs[i].name,unpack(tabtextcolor)) if i==ctab then tpt.fillrect(wx+ox,wy,#tabs[i].name*6+3,12,unpack(ctabcolor)) end ox=ox+#tabs[i].name*6+2 end -- Nicklist tpt.drawrect(wx+496,wy+12,104,336,unpack(wcolor)) for i,v in ipairs(tabs[ctab].nicks) do if i<=33 then if v:gsub("[^@+]","")=="@" then drawtext(wx+498,wy+4+10*i,v,unpack(opcolor)) elseif v:gsub("[^@+]","")=="+" then drawtext(wx+498,wy+4+10*i,v,unpack(voicecolor)) else drawtext(wx+498,wy+4+10*i,v,unpack(nickcolor)) end end end tpt.drawrect(wx,wy+24,496,324,unpack(wcolor)) tpt.drawrect(wx+587,wy+348,13,12,unpack(wcolor)) drawtext(wx+589,wy+351,">>",unpack(wcolor)) -- Text for i=1,math.min(#tabs[ctab].text,32) do drawtext(wx+2,wy+348-i*10,r(textcolor)..tabs[ctab].text[i]) end drawtext(wx+2,wy+351,edit,unpack(textcolor)) if math.floor(cursorbl/10)%2==1 then tpt.fillrect(wx+2+textwidth(edit:sub(1,cpos)),wy+354,textwidth((edit:sub(cpos+1,cpos+1).." "):sub(1,1))+2,4,unpack(textcolor)) end cursorbl=cursorbl+1 end local function isinrect(x,y,xz,yz,w,h) return x>=xz and y>=yz and x<xz+w and y<yz+h end local function wprint(n,s) local t={[0]="\1\1\1","\255\255\255","\1\1\128","\1\128\1","\255\1\1","\128\1\1","\128\1\128","\255\128\1","\255\255\1","\1\255\1","\1\128\128","\1\255\255"} s=s:gsub("\3(%d%d?)",function(i)return t[tonumber(i)]and"\15"..t[tonumber(i)]or""end) while #s>0 do print(s) local f=#s while textwidth(s:sub(1,f))>493 do f=f-1 end table.insert(tabs[n].text,1,s:sub(1,f)) s=s:sub(f+1) end end local function findtab(s) for i,v in ipairs(tabs) do if s:lower()==v.name:lower() then return i end end end local function resetwindows() tabs={{name="-server-", nicks={}, topic=version, text={"--- "..version.." ---","The idea becomes real","Line by line"}}} ctab=1 edit="" cpos=0 end local function send() table.insert(history,1,edit) cpos=0 if edit:match"^/" then local command,params=edit:match"/(%S+)%s*(.*)" if command:lower()=="clear" then if params~="" then (tabs[findtab(params)or tonumber(params)or -1]or {}).text={} else tabs[ctab].text={} end elseif command:lower()=="close" or command:lower()=="part" then if params~="" then table.remove(tabs,findtab(params)or tonumber(params) or 0) else table.remove(tabs,ctab) end elseif command:lower()=="open" or command:lower()=="query" then table.insert(tabs,{name=params,nicks={},topic="",text={}}) elseif command:lower()=="ctcp" then local who,what=params:match"(%S+)%s*(.*)" c:send("PRIVMSG "..who.." :\1"..what.."\1\n") elseif command:lower()=="me" then c:send("PRIVMSG "..tabs[ctab].name.." :\1ACTION "..params.."\1\n") wprint(ctab,r(mecolor).."* "..mynick.." "..params) elseif command:lower()=="server" then if c then pcall(c.send,c,"QUIT\n") pcall(c.close,c) c=nil end resetwindows() c=socket.tcp() if params:find"%S" then local server,port=params:match"(%S+)%s+(%S+)" server=server or params port=port or 6667 c:connect(server,tonumber(port)) else c:connect(conf.server,conf.port) end c:settimeout(0) else c:send(edit:match"/(.*)".."\n") wprint(ctab,">"..edit) end else c:send("PRIVMSG "..tabs[ctab].name.." :"..edit.."\n") wprint(ctab,r(mycolor).."<"..mynick.."> "..edit) end edit="" end local function mouse(x,y,b,n) if n==1 then -- Tabs local ox=0 for i=1,#tabs do if isinrect(x,y,wx+ox,wy,#tabs[i].name*6+3,12) then ctab=i end ox=ox+#tabs[i].name*6+2 end if isinrect(x,y,wx+587,wy+348,13,12) then send() end end return false end local function resortnicks(n) local d=tabs[n].nicks local a,b={},{} for _,v in ipairs(d) do a[#a+1],b[#b+1]=v:match"([@+]?)(.*)" end local op,vc,rg={},{},{} for i,v in ipairs(a) do if v=="@" then table.insert(op,b[i]) elseif v=="+" then table.insert(vc,b[i]) else table.insert(rg,b[i]) end end local function f(a,b)return a:lower()<b:lower() end table.sort(op,f) table.sort(vc,f) table.sort(rg,f) for i,_ in ipairs(d) do d[i]=nil end for _,v in ipairs(op) do table.insert(d,"@"..v) end for _,v in ipairs(vc) do table.insert(d,"+"..v) end for _,v in ipairs(rg) do table.insert(d,v) end end local function receive() local s,e=c:receive"*l" if s then s=s:gsub("\n","") local sender,command,params=s:match":(%S+) (%S+)%s*(.*)" if not sender then command,params=s:match"(%S+)%s*(.*)" end if s:match"(.-)NOTICE %* :%*%*%* Checking Ident" then c:send("NICK "..mynick.."\n") c:send("USER xstptirc _ _ :TPT login:"..n.."\n") elseif command:lower()=="join" then local channel=params:match"(%S+)" local nick=sender:match"([^!]+)" if nick:lower()==mynick:lower() then table.insert(tabs,{name=channel,nicks={},topic="",text={}}) ctab=#tabs wprint(1,r(chancolor).."*** You have joined "..channel) else table.insert(tabs[findtab(channel)or 1].nicks,nick) end wprint(findtab(channel)or 1,r(chancolor).."*** "..nick.." has joined "..channel) resortnicks(findtab(channel)or 1) elseif command=="376" then for _,v in ipairs(conf.autojoin) do c:send("JOIN "..v.."\n") end elseif command:lower()=="privmsg" then local channel,text=params:match"(%S+) :(.*)" local nick=sender:match"([^!]+)" if text:find"\1" then local command,params=text:match"\1(%S+)%s*(.*)\1" if command:lower()=="action" then if channel:lower()==mynick:lower() then if not findtab(nick) then table.insert(tabs,{name=nick,nicks={},topic="Query with "..nick,text={}}) end channel=nick end wprint(findtab(channel)or 1,"* "..nick.." "..params) else local ctcp={ping=function(p)return p end,time=function(p)return os.date"%c"end,version=function(p)return version end,userinfo=function(p)return "PING TIME VERSION USERINFO"end} if ctcp[command:lower()] then c:send("NOTICE "..nick.." :\1"..command.." "..ctcp[command:lower()](params).."\1\n") end end else if channel:lower()==mynick:lower() then if not findtab(nick) then table.insert(tabs,{name=nick,nicks={},topic="Query with "..nick,text={}}) end channel=nick end for _,v in ipairs(tabs[ctab].nicks) do if v:gsub("[@+]",""):lower()==nick:lower() then if v:gsub("[^@+]","")=="@" then nick=r(opcolor)..nick..r(textcolor) elseif v:gsub("[^@+]","")=="+" then nick=r(voicecolor)..nick..r(textcolor) end end end wprint(findtab(channel)or 1,"<"..nick.."> "..text) end elseif command=="433" then mynick=mynick.."-" c:send("NICK "..mynick.."\n") elseif command=="332" then local channel,topic=params:match"%S+ (%S+) :(.*)" tabs[findtab(channel)or 1].topic=topic wprint(findtab(channel)or 1,r(chancolor).."*** Topic for "..channel.." is: ") wprint(findtab(channel)or 1,r(chancolor)..topic) elseif command=="333" then local channel,whom,when=params:match"%S+ (%S+) (%S+) (%S+)" wprint(findtab(channel)or 1,r(chancolor).."*** Topic set by "..whom.." at "..os.date("%c",when)) elseif command=="353" then local channel,nicks=params:match"%S+ = (%S+) :(.*)" for nick in nicks:gmatch"(%S+)" do table.insert(tabs[findtab(channel)or 0].nicks,nick) end resortnicks(findtab(channel)or 0) elseif command=="366" then elseif command:lower()=="ping" then c:send("PONG\n") elseif command:lower()=="part" then local channel=params:match"(%S+)" local nick=sender:match"([^!]+)" for i,v in ipairs(tabs[findtab(channel)or 0].nicks) do if v:lower():gsub("[@+]","")==nick:lower() then table.remove(tabs[findtab(channel)or 1].nicks,i) break end end wprint(findtab(channel)or 1,r(chancolor).."*** "..nick.." has left "..channel) if nick:lower()==mynick:lower() then table.remove(tabs,findtab(channel)or -1) wprint(1,r(chancolor).."*** You have left "..channel) end elseif command:lower()=="quit" then local nick=sender:match"([^!]+)" local reason=params:match"%s*:(.*)" for i,v in ipairs(tabs) do for j,n in ipairs(v.nicks) do if n:lower():gsub("[@+]","")==nick:lower() then wprint(i,r(chancolor).."*** "..nick.." has quit ("..reason..")") table.remove(v.nicks,j) break end end end elseif command:lower()=="mode" then local nick=sender:match"([^!]+)" local whom,modes=params:match"(%S+) (.*)" wprint(findtab(whom)or 1,r(chancolor).."*** "..nick.." sets modes for "..whom.." : "..modes) if whom:match"#" then tabs[findtab(whom)or 1].nicks={} c:send("NAMES "..whom.."\n") end elseif command:lower()=="nick" then local nick=sender:match"([^!]+)" local newnick=params:match":(%S+)" for i,v in ipairs(tabs) do for j,n in ipairs(v.nicks) do if n:lower():gsub("[@+]","")==nick:lower() then wprint(i,r(chancolor).."*** "..nick.." has changed nick to "..newnick) v.nicks[j]=n:gsub("[^@+]","")..newnick break end end end if nick:lower()==mynick:lower() then mynick=newnick end elseif command:lower()=="notice" then local nick=sender:match"([^!]+)" local text=params:match"%S+ :(.*)" wprint(ctab,"-- "..nick.." -- "..text) else wprint(1,s) end else if e~="timeout" then wprint(ctab,"*** ERROR READING FROM SOCKET: "..e) c=nil end end end local function caps(a) local from="`1234567890-=qwertyuiop[]\\asdfghjkl;'zxcvbnm,./" local to= "~!@#$%^&*()_+QWERTYUIOP{}|ASDFGHJKL:\"ZXCVBNM<>?" local s,e=from:find(a,1,true) return to:sub(s,e) or a end local function key(a,b,c,d) if d==1 then if b>31 and b<127 then edit=edit:sub(1,cpos)..(c%4==0 and string.char(b) or caps(string.char(b)))..edit:sub(cpos+1,#edit) cpos=cpos+1 elseif b==13 then send() elseif b==8 then edit=edit:sub(1,math.max(cpos-1,0))..edit:sub(cpos+1,#edit) cpos=cpos-1 if cpos<0 then cpos=0 end elseif b==127 then edit=edit:sub(1,cpos)..edit:sub(cpos+2,#edit) elseif b==9 then local txt,lw=edit:match"(.*%s)(%S+)" lw=lw or edit txt=txt or "" for _,v in ipairs(tabs[ctab].nicks) do if v:gsub("[@+]",""):sub(1,#lw):lower()==lw:lower() then edit=txt..v:gsub("[@+]","") end end elseif b==276 then cpos=cpos-1 if cpos<0 then cpos=0 end elseif b==275 then cpos=cpos+1 if cpos>#edit then cpos=#edit end elseif b==274 then histp=histp-1 if cpos<0 then cpos=0 end edit=history[histp] cpos=#edit elseif b==273 then histp=histp+1 if histp>#history then histp=#history end edit=history[histp] cpos=#edit else return true end end return false end local launched=false local visible=false local function appear() if not visible then tpt.register_step(ui) tpt.register_mouseclick(mouse) tpt.register_keypress(key) end visible=true end local function disappear() if visible then tpt.unregister_step(ui) tpt.unregister_mouseclick(mouse) tpt.unregister_keypress(key) end visible=false end local function start() if not launched then resetwindows() c=socket.tcp() c:connect(conf.server,conf.port) c:settimeout(0) tpt.register_step(receive) end launched=true end local function stop() if launched then c:send"QUIT\n" c:close() c=nil tpt.unregister_step(receive) end disappear() launched=false end local function keyhandler(a,b,c,d) if d==1 and (math.floor(c/64)%2==1 or math.floor(c/128)%2==1) then if a=="m" then if c%4==0 then if visible then disappear() else if not launched then start() end appear() end else disappear() stop() end return false end end end tpt.register_keypress(keyhandler)