lib/marvin/abstract_client.rb in jeffrafter-marvin-0.1.20081115 vs lib/marvin/abstract_client.rb in jeffrafter-marvin-0.1.20081120

- old
+ new

@@ -5,12 +5,21 @@ module Marvin class AbstractClient include Marvin::Dispatchable + def initialize(opts = {}) + self.original_opts = opts.dup # Copy the options so we can use them to reconnect. + self.server = opts[:server] + self.port = opts[:port] + self.default_channels = opts[:channels] + self.nicks = opts[:nicks] || [] + self.pass = opts[:pass] + end + cattr_accessor :events, :configuration, :logger, :is_setup, :connections - attr_accessor :channels, :nickname + attr_accessor :channels, :nickname, :server, :port, :nicks, :pass, :disconnect_expected, :original_opts # Set the default values for the variables self.events = [] self.configuration = OpenStruct.new self.configuration.channels = [] @@ -20,23 +29,29 @@ # current connection, dispatching a :client_connected event # once it has finished. During this process, it will # call #client= on each handler if they respond to it. def process_connect self.class.setup - logger.debug "Initializing the current instance" + logger.info "Initializing the current instance" self.channels = [] self.connections << self - logger.debug "Setting the client for each handler" + logger.info "Setting the client for each handler" self.handlers.each { |h| h.client = self if h.respond_to?(:client=) } - logger.debug "Dispatching the default :client_connected event" + logger.info "Dispatching the default :client_connected event" dispatch :client_connected end def process_disconnect + logger.info "Handling disconnect for #{self.server}:#{self.port}" self.connections.delete(self) if self.connections.include?(self) dispatch :client_disconnected - Marvin::Loader.stop! if self.connections.blank? + unless self.disconnect_expected + logger.warn "Lost connection to server - adding reconnect" + self.class.add_reconnect self.original_opts + else + Marvin::Loader.stop! if self.connections.blank? + end end # Sets the current class-wide settings of this IRC Client # to either an OpenStruct or the results of #to_hash on # any other value that is passed in. @@ -49,15 +64,10 @@ # will convert the channel option of the configuration # to be channels - hence normalising it into a format # that is more widely used throughout the client. def self.setup return if self.is_setup - # Default the logger back to a new one. - self.configuration.channels ||= [] - unless self.configuration.channel.blank? || self.configuration.channels.include?(self.configuration.channel) - self.configuration.channels.unshift(self.configuration.channel) - end if configuration.logger.blank? require 'logger' configuration.logger = Marvin::Logger.logger end self.logger = self.configuration.logger @@ -78,44 +88,43 @@ # on the client. Usually, this will send the user command, # set out nick, join all of the channels / rooms we wish # to be in and if a password is specified in the configuration, # it will also attempt to identify us. def handle_client_connected(opts = {}) - logger.debug "About to handle post init" + logger.info "About to handle client connected" + # If the pass is set + unless self.pass.blank? + logger.info "Sending pass for connection" + command :pass, self.pass + end # IRC Connection is establish so we send all the required commands to the server. - logger.debug "Setting default nickname" - default_nickname = self.configuration.nick || self.configuration.nicknames.shift + logger.info "Setting default nickname" + default_nickname = self.nicks.shift nick default_nickname - logger.debug "sending user command" + logger.info "sending user command" command :user, self.configuration.user, "0", "*", Marvin::Util.last_param(self.configuration.name) - # If a password is specified, we will attempt to message - # NickServ to identify ourselves. - say ":IDENTIFY #{self.configuration.password}", "NickServ" unless self.configuration.password.blank? - # Join the default channels - self.configuration.channels.each { |c| self.join c } rescue Exception => e Marvin::ExceptionTracker.log(e) end - - # The default handler for when a users nickname is taken on - # on the server. It will attempt to get the nicknickname from - # the nicknames part of the configuration (if available) and - # will then call #nick to change the nickname. - def handle_incoming_nick_taken(opts = {}) - logger.info "Nick Is Taken" - logger.debug "Available Nicknames: #{self.configuration.nicknames.to_a.join(", ")}" - available_nicknames = self.configuration.nicknames.to_a - if available_nicknames.length > 0 - logger.debug "Getting next nickname to switch" - next_nick = available_nicknames.shift # Get the next nickname - self.configuration.nicknames = available_nicknames - logger.info "Attemping to set nickname to #{new_nick}" - nick next_nick - else - logger.info "No Nicknames available - QUITTING" - quit + + def default_channels + @default_channels ||= [] + end + + def default_channels=(channels) + @default_channels = channels.to_a.map { |c| c.to_s } + end + + def nicks + if @nicks.blank? && !@nicks_loaded + logger.info "Setting default nick list" + @nicks = [] + @nicks << self.configuration.nick + @nicks += self.configuration.nicks.to_a unless self.configuration.nicks.blank? + @nicks_loaded end + return @nicks end # The default response for PING's - it simply replies # with a PONG. def handle_incoming_ping(opts = {}) @@ -124,32 +133,67 @@ end # TODO: Get the correct mapping for a given # Code. def handle_incoming_numeric(opts = {}) + case opts[:code] + when Marvin::IRC::Replies[:RPL_WELCOME] + handle_welcome + when Marvin::IRC::Replies[:ERR_NICKNAMEINUSE] + handle_nick_taken + end code = opts[:code].to_i args = Marvin::Util.arguments(opts[:data]) - dispatch :incoming_numeric_processed, {:code => code, :data => args} + dispatch :incoming_numeric_processed, :code => code, :data => args end + def handle_welcome + logger.info "Say hello to my little friend - Got welcome" + # If a password is specified, we will attempt to message + # NickServ to identify ourselves. + say ":IDENTIFY #{self.configuration.password}", "NickServ" unless self.configuration.password.blank? + # Join the default channels IF they're already set + # Note that Marvin::IRC::Client.connect will set them AFTER this stuff is run. + self.default_channels.each { |c| self.join(c) } + end + + # The default handler for when a users nickname is taken on + # on the server. It will attempt to get the nicknickname from + # the nicknames part of the configuration (if available) and + # will then call #nick to change the nickname. + def handle_nick_taken + logger.info "Nickname '#{self.nickname}' on #{self.server} taken, trying next." + logger.info "Available Nicknames: #{self.nicks.empty? ? "None" : self.nicks.join(", ")}" + if !self.nicks.empty? + logger.info "Getting next nickname to switch" + next_nick = self.nicks.shift # Get the next nickname + logger.info "Attemping to set nickname to '#{next_nick}'" + nick next_nick + else + logger.fatal "No Nicknames available - QUITTING" + quit + end + end + ## General IRC Functions # Sends a specified command to the server. # Takes name (e.g. :privmsg) and all of the args. # Very simply formats them as a string correctly # and calls send_data with the results. def command(name, *args) # First, get the appropriate command name = name.to_s.upcase args = args.flatten.compact - irc_command = "#{name} #{args.join(" ").strip} \r\n" + irc_command = "#{name} #{args.join(" ").strip}\r\n" send_line irc_command end def join(channel) channel = Marvin::Util.channel_name(channel) # Record the fact we're entering the room. + # TODO: Refactor to only add the channel when we receive confirmation we've joined. self.channels << channel command :JOIN, channel logger.info "Joined channel #{channel}" dispatch :outgoing_join, :target => channel end @@ -164,16 +208,17 @@ logger.warn "Tried to disconnect from #{channel} - which you aren't a part of" end end def quit(reason = nil) - logger.debug "Preparing to part from #{self.channels.size} channels" + self.disconnect_expected = true + logger.info "Preparing to part from #{self.channels.size} channels" self.channels.to_a.each do |chan| - logger.debug "Parting from #{chan}" + logger.info "Parting from #{chan}" self.part chan, reason end - logger.debug "Parted from all channels, quitting" - command :quit + logger.info "Parted from all channels, quitting" + command :quit dispatch :quit # Remove the connections from the pool self.connections.delete(self) logger.info "Quit from server" end \ No newline at end of file