Class: Marvin::AbstractClient

Inherits:
Object
  • Object
show all
Includes:
Dispatchable
Defined in:
lib/marvin/abstract_client.rb

Direct Known Subclasses

IRC::Client, TestClient

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Dispatchable

included

Constructor Details

#initialize(opts = {}) ⇒ AbstractClient

Returns a new instance of AbstractClient.



10
11
12
13
14
15
16
17
# File 'lib/marvin/abstract_client.rb', line 10

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

Instance Attribute Details

#channelsObject

Returns the value of attribute channels.



20
21
22
# File 'lib/marvin/abstract_client.rb', line 20

def channels
  @channels
end

#disconnect_expectedObject

Returns the value of attribute disconnect_expected.



20
21
22
# File 'lib/marvin/abstract_client.rb', line 20

def disconnect_expected
  @disconnect_expected
end

#nicknameObject

Returns the value of attribute nickname.



20
21
22
# File 'lib/marvin/abstract_client.rb', line 20

def nickname
  @nickname
end

#nicksObject

Returns the value of attribute nicks.



20
21
22
# File 'lib/marvin/abstract_client.rb', line 20

def nicks
  @nicks
end

#original_optsObject

Returns the value of attribute original_opts.



20
21
22
# File 'lib/marvin/abstract_client.rb', line 20

def original_opts
  @original_opts
end

#passObject

Returns the value of attribute pass.



20
21
22
# File 'lib/marvin/abstract_client.rb', line 20

def pass
  @pass
end

#portObject

Returns the value of attribute port.



20
21
22
# File 'lib/marvin/abstract_client.rb', line 20

def port
  @port
end

#serverObject

Returns the value of attribute server.



20
21
22
# File 'lib/marvin/abstract_client.rb', line 20

def server
  @server
end

Class Method Details

.configuration=(config) ⇒ Object

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.



58
59
60
# File 'lib/marvin/abstract_client.rb', line 58

def self.configuration=(config)
  @@configuration = config.is_a?(OpenStruct) ? config : OpenStruct.new(config.to_hash)
end

.setupObject

Initializes class-wide settings and those that are required such as the logger. by default, it 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.



67
68
69
70
71
72
73
74
75
# File 'lib/marvin/abstract_client.rb', line 67

def self.setup
  return if self.is_setup
  if configuration.logger.blank?
    require 'logger'
    configuration.logger = Marvin::Logger.logger
  end
  self.logger = self.configuration.logger
  self.is_setup = true
end

Instance Method Details

#action(target, message) ⇒ Object



233
234
235
236
237
238
# File 'lib/marvin/abstract_client.rb', line 233

def action(target, message)
  action_text = Marvin::Util.last_param "\01ACTION #{message.strip}\01"
  command :privmsg, target, action_text
  dispatch :outgoing_action, :target => target, :message => message
  logger.info "Action sent to #{target} - #{message}"
end

#command(name, *args) ⇒ Object

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.



183
184
185
186
187
188
189
# File 'lib/marvin/abstract_client.rb', line 183

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"
  send_line irc_command
end

#default_channelsObject



109
110
111
# File 'lib/marvin/abstract_client.rb', line 109

def default_channels
  @default_channels ||= []
end

#default_channels=(channels) ⇒ Object



113
114
115
# File 'lib/marvin/abstract_client.rb', line 113

def default_channels=(channels)
  @default_channels = channels.to_a.map { |c| c.to_s }
end

#handle_client_connected(opts = {}) ⇒ Object

The default handler for all things initialization-related 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.



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/marvin/abstract_client.rb', line 92

def handle_client_connected(opts = {})
  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.info "Setting default nickname"
  default_nickname = self.nicks.shift
  nick default_nickname
  logger.info "sending user command"
  command :user, self.configuration.user, "0", "*", Marvin::Util.last_param(self.configuration.name)
rescue Exception => e
  Marvin::ExceptionTracker.log(e)
end

#handle_incoming_numeric(opts = {}) ⇒ Object

TODO: Get the correct mapping for a given Code.



137
138
139
140
141
142
143
144
145
146
147
# File 'lib/marvin/abstract_client.rb', line 137

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
end

#handle_incoming_ping(opts = {}) ⇒ Object

The default response for PING’s - it simply replies with a PONG.



130
131
132
133
# File 'lib/marvin/abstract_client.rb', line 130

def handle_incoming_ping(opts = {})
  logger.info "Received Incoming Ping - Handling with a PONG"
  pong(opts[:data])
end

#handle_nick_takenObject

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.



163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/marvin/abstract_client.rb', line 163

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

#handle_welcomeObject



149
150
151
152
153
154
155
156
157
# File 'lib/marvin/abstract_client.rb', line 149

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

#join(channel) ⇒ Object



191
192
193
194
195
196
197
198
199
# File 'lib/marvin/abstract_client.rb', line 191

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

#msg(target, message) ⇒ Object



227
228
229
230
231
# File 'lib/marvin/abstract_client.rb', line 227

def msg(target, message)
  command :privmsg, target, Marvin::Util.last_param(message)
  logger.info "Message sent to #{target} - #{message}"
  dispatch :outgoing_message, :target => target, :message => message
end

#nick(new_nick) ⇒ Object



246
247
248
249
250
251
252
# File 'lib/marvin/abstract_client.rb', line 246

def nick(new_nick)
  logger.info "Changing nickname to #{new_nick}"
  command :nick, new_nick
  self.nickname = new_nick
  dispatch :outgoing_nick, :new_nick => new_nick
  logger.info "Nickname changed to #{new_nick}"
end

#part(channel, reason = nil) ⇒ Object



201
202
203
204
205
206
207
208
209
210
# File 'lib/marvin/abstract_client.rb', line 201

def part(channel, reason = nil)
  channel = Marvin::Util.channel_name(channel)
  if self.channels.include?(channel)
    command :part, channel, Marvin::Util.last_param(reason)
    dispatch :outgoing_part, :target => channel, :reason => reason
    logger.info "Parted from room #{channel}#{reason ? " - #{reason}" : ""}"
  else
    logger.warn "Tried to disconnect from #{channel} - which you aren't a part of"
  end
end

#pong(data) ⇒ Object



240
241
242
243
244
# File 'lib/marvin/abstract_client.rb', line 240

def pong(data)
  command :pong, data
  dispatch :outgoing_pong
  logger.info "PONG sent to #{data}"
end

#process_connectObject

Initializes the instance variables used for the 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.



32
33
34
35
36
37
38
39
40
41
# File 'lib/marvin/abstract_client.rb', line 32

def process_connect
  self.class.setup
  logger.info "Initializing the current instance"
  self.channels = []
  self.connections << self
  logger.info "Setting the client for each handler"
  self.handlers.each { |h| h.client = self if h.respond_to?(:client=) }
  logger.info "Dispatching the default :client_connected event"
  dispatch :client_connected
end

#process_disconnectObject



43
44
45
46
47
48
49
50
51
52
53
# File 'lib/marvin/abstract_client.rb', line 43

def process_disconnect
  logger.info "Handling disconnect for #{self.server}:#{self.port}"
  self.connections.delete(self) if self.connections.include?(self)
  dispatch :client_disconnected
  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

#quit(reason = nil) ⇒ Object



212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/marvin/abstract_client.rb', line 212

def quit(reason = nil)
  self.disconnect_expected = true
  logger.info "Preparing to part from #{self.channels.size} channels"
  self.channels.to_a.each do |chan|
    logger.info "Parting from #{chan}"
    self.part chan, reason
  end
  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

#receive_line(line) ⇒ Object

Handling all of the the actual client stuff.



79
80
81
82
83
# File 'lib/marvin/abstract_client.rb', line 79

def receive_line(line)
  dispatch :incoming_line, :line => line
  event = Marvin::Settings.default_parser.parse(line)
  dispatch(event.to_incoming_event_name, event.to_hash) unless event.nil?
end