-- This is the main class file -- Written by Moritz Hoffmann 2006, part of the orx-irc project -- Released under the BSD license -- The following lines will be kept and shop up as the beginning lines of the -- final irc.cls file. /**************************************************************************** ** This is the object rexx IRC interface class ** If you find just one file called irc.cls you have a compiled version ** of the source. Go to sf.net/projects/orx-irc to get the full source ** and the documentation. ** There is a subversion tree a well. ****************************************************************************/ -- ************************************************************************* -- ** The IRC Class -- ** This is the main class of the irc client. All communication with the server -- ** passes the class -- ************************************************************************* ::CLASS irc PUBLIC -- initialization, accepts a host object ::METHOD init expose users channels NumHandler ClientEvent TalkBack -- an array that contains the current user objects, ID is the position in the array users = .array~new -- an array that contains the current active channel objects and their ID channels = .array~new -- a handler to handle incoming numeric messages NumHandler = .NumHandler~new(self) -- initialize the ClientEvent object ClientEvent = .ClientEvent~new -- create the talkback object TalkBack = .ClientTB~New(self,NumHandler) -- a directory to hold specific information about the IRC server self~settings = .directory~new self~settings~IsSupported = .Issupported~new -- debug that the class has been created succesfully call debug "[IRC instance created]",1 -- return the current instance return self -- make the users array public ::METHOD users ATTRIBUTE -- make the channels object public ::METHOD channels ATTRIBUTE -- give access to the Talkback object ::METHOD TalkBack UNGUARDED expose TalkBack; return TalkBack -- make the settings accessible ::METHOD Settings ATTRIBUTE -- method connect connects to an IRC server -- call: ~connect(host object,password,default nick name, user name, comment) ::METHOD Connect expose host serverpass socket -- tell the client the object has been initialized self~ClientEvent("irc.event.init",self) -- get the arguments use arg host, serverpass, nick, username, description call debug "[IRC: Connect method called]",1 -- create a new line based socket socket = .LNSOCKET~new(host,self) -- now connect rc = socket~connect if rc \= 0 then do call debug "[IRC: Connect: socket return value:" rc", error connecting!", 16 return end call debug "[IRC: Connect: socket connected]",8 call SysSleep 0.5 if serverpass \= .nil then do call debug "[IRC: Connect: PASS command will be written]",8 self~writeln('PASS '||serverpass) end self~writeln('NICK' nick) self~writeln('USER' username '7 * :'description) call debug "[IRC: Connect: NICK and USER command written]",8 -- .user~new(self,nick) -- creates user with ID=1, the own user -- the connect event is created by num rpl 001 to make sure the connection is up -- when the command is issued -- self~ClientEvent("irc.event.connect") ::METHOD DisConnect expose socket if socket~connected then do self~Writeln("QUIT :"arg(1)) call debug "[IRC: DisConnect: Disconnecting]",1 socket~disconnect self~ClientEvent("IRC.EVENT.DISCONNECT") end else call debug "[IRC DisConnect: Not connected, can not disconnect",16 ::METHOD Connected expose socket return socket~connected ::METHOD Writeln expose socket call debug "[IRC: WriteLN:" arg(1),3 socket~write(arg(1)||"0d0a"x) self~ClientEvent("IRC.EVENT.WRITE",arg(1)) ::METHOD GetEventHandler expose ClientEvent return ClientEvent -- This method forwards events to the client ::METHOD ClientEvent expose ClientEvent signal on syntax name err -- the argument count is 2, meaning the second parameter was supplied, -- which is the event's parameter if arg() = 2 then do -- get the name and message argument use arg name,message eventmessage =.message~new(ClientEvent,name,'I', message)~~send end -- only the name was supplied else do use arg name eventmessage =.message~new(ClientEvent,name)~~send end return -- forward all errors to the debug object to protect the main thread from -- terminating err: .debug~error(condition('O')) -- This message gets called whenever there is new input from the socket ::METHOD Available -- add a blank line to the debug output to improve readability call debug ""time(),1 call debug "[IRC: Available: |"arg(1), 3 -- forward the incoming string to the private parseline method message = self~parseline(arg(1)) -- if a message was returned it needs to be handled by the local event handler if message \= .nil then self~Event(message) -- this method receives all events that come from the server ::METHOD Event expose NumHandler use arg message -- ****************************** -- This method receives the messages created by the message parser. All -- transformed messages that arrive pass through this method. -- To forward the messages to their appropriate receiver objects, the -- superclasses of the messages are analyzed. -- For now, there's a receiver object for numerc replies. -- ******************************** call debug TSToStr(message~Timestamp) "[IRC: Event:" message~class,3 if message= .nil then raise syntax 93.911 superclasses = message~class~superclasses -- filter numeric replies if message~class = .nummsg then do NumHandler~event(message) self~ClientEvent("IRC.EVENT.NUMRPL."message~number,message) return end -- here all other messages get forwarded to their reciever do superclass over superclasses -- filter for server messages if superclass = .smsg then do -- filter for .smsg superclass SELECT when message~class = .msgerror then do call debug "[IRC: Event: Error received, disconnecting!",9 self~disconnect return end when message~class = .msgping then self~writeln("PONG :"message~string) when message~class = .snotice then self~ClientEvent("IRC.EVENT.SNOTICE",message) otherwise nop END return end if superclass = .umsg then do message~sender~event(message) return end if superclass = .umsgr then do message~sender~event(message) return end end -- This is the parsing method -- It converts the incoming string to it's appropriate message object -- and returns it. If it failed .nil is returned instead. ::METHOD parseline PRIVATE expose events channels users call debug "[IRC: ParseLine",1 parse arg line parse var line prefix command params params=params~strip command = command~strip -- there was no prefix, the prefix *always* starts with a colon if prefix~left(1) \= ':' then do params = command command = prefix prefix = '' origin='SERVER' end else do prefix = prefix~right(prefix~length-1)~strip parse var prefix nick'!'username'@'host if nick~pos('.') = 0 then do -- Message is from a server, nicks never contain dots -- This workaround fails if the server name doesn't contain dots... origin = 'NICK' end else origin = 'SERVER' end message=.nil call debug "[IRC: ParseLine: origin='"origin"'",8 if origin = 'SERVER' then -- it's a numeric reply if command~datatype = 'NUM' then message=.nummsg~new(command,params) else do Select when command = 'PING' then do parse var params . ':'ping_str message=.msgping~new(ping_str) end WHEN command = 'ERROR' then message=.msgerror~new WHEN command = 'NOTICE' then do if prefix = '' then parse var line ':' text else parse var params ':' text message=.snotice~new(text) end WHEN command = 'WALLOPS' then do if prefix = '' then parse var line ':' text else parse var params ':' text message=.msgwallops~new(text) end WHEN command = "MODE" then do if prefix = '' then parse var line . target modifier else parse var params target modifier message = .smode~new(prefix,self~GetreceiverObject(target),modifier) end otherwise nop END end else do nickobject = self~GetReceiverObject(nick) if nickobject = .nil then do nickobject = .user~new(self,prefix,users,channels) end if (\ nickobject~hashostmark) & (username <> '') & (host <> '') then do nickobject~SetHostMark(nickobject~makestring'!'username'@'host) end parse var params commands ':'last commands = commands~strip; last=last~strip SELECT WHEN command = 'NICK' then message= .msgnick~new(nickobject,last) WHEN command = 'QUIT' then message= .msgquit~new(nickobject,last) WHEN command = 'KILL' then do message= .msgkill~new(nickobject, self~GetReceiverObject(commands), last) end WHEN command = 'JOIN' then message= .msgjoin~new(nickobject,last) WHEN command = 'PART' then message= .msgpart~new(nickobject,self~GetReceiverObject(commands),last) WHEN command = 'KICK' then do parse value commands~space with channel targetnick message=.msgkick~new(nickobject,self~GetReceiverObject(targetnick),self~GetReceiverObject(channel),last) end WHEN command = 'TOPIC' then do message= .msgtopic~new(nickobject,self~GetReceiverObject(commands),last) end WHEN command = 'MODE' then do parse var params receiver string message= .msgmode~new(nickobject,self~GetReceiverObject(receiver),string) end WHEN command = 'PRIVMSG' then do message= .privmsg~new(nickobject,self~GetReceiverObject(commands),last) end WHEN command = 'NOTICE' then do message= .unotice~new(nickobject,self~GetReceiverObject(commands),last) end WHEN command = 'INVITE' then do message= .invite~new(nickobject,self~GetReceiverObject(commands),last) end OTHERWISE call debug "[IRC: ParseLine:" nickobject~nick 'sent unknown command' command '(params='params')',24 END end return message -- this message returns a channel or nick object if it's known or the nil object ::METHOD GetReceiverObject expose users channels parse arg string call debug "[IRC: GetReceiverObject:" string,3 string = IRCUpper(string) if users~items>0 then do i = 1 to users~last if users~hasindex(i) then if IRCUpper(users[i]) = string then do call debug "[IRC: GetReceiverObject: found" string,4 return users[i] end end if channels~items>0 then do i = 1 to channels~last if channels~hasindex(i) then if IRCUpper(channels[i]) = string then do call debug "[IRC: GetReceiverObject: found" string "id:" i,4 return channels[i] end end call debug "[IRC: GetReceiverObject:" string "not found, returning NIL.",4 return .nil