Class: Participle::Bot

Inherits:
Object
  • Object
show all
Includes:
CLI, Colors, Events, Log, Token
Defined in:
lib/bot.rb

Overview

The basic bot class.

Constant Summary

PARTICIPLE_VERSION =

what do you think?

"1.4.0"

Constants included from Colors

Colors::ANSI_COLORS_HEX, Colors::ANSI_COLORS_NAMED, Colors::ANSI_STYLES

Instance Attribute Summary (collapse)

Instance Method Summary (collapse)

Methods included from CLI

#add_cli_command

Methods included from Token

#get_token, to_qstring

Methods included from Events

#bind_event, #unbind_last

Methods included from Log

#add_logger, #error_display, #flush_logs, #log, #remove_logger, #setup_logging

Methods included from Colors

#ansi_colorize, #ansi_stylize, #chromaticied, #closest_to, #color_difference, #colorize, #embolden, #format_html, #format_user, #get_term_size, #italicize, #loggerify, #room_name, #timestamp, #to_hex, #unchrome, #underscore

Constructor Details

- (Bot) initialize { ... }

Creates a new bot.

Examples:

Loads the configuration from the configuration{} block

Participle::Bot.new{|b|
  b.configuration{|conf|
    conf.nick = "participle"
    conf.admin = "incluye"
    etc.
  }
}

Loads the configuration from conf/conf.yaml

Participle::Bot.new()

Yields:

  • The block with which to initialize the bot (optional)



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/bot.rb', line 89

def initialize &blk
  @silent = false
  @modules = {}
  
  @eventhandler = Suplex.new
  
  @reconnect_wait = 0
  
  @__cli_buffer = ""
  
  # commands for each message type
  @_commands = {:join => [], :message => [], :part => [], :botjoin => [], :botpart => []}
  
  # hash, key names are room names and values are arrays of usernames
  @rooms    = {}
  
  # bot configuration!
  @conf     = OpenStruct.new :version => PARTICIPLE_VERSION, :trigger => "!", :blacklist => [],
                             :logging => true, :pretty_display => true
                             
  @__conv = Iconv.new("UTF-8", "LATIN1")
  
  # server connection
  @server   = TCPSocket.new "chat.deviantart.com", 3900
  
  @buffer   = Queue.new
  
  Extension.bot = self
  Message.bot = self
  
  self.bind_event("dAmnServer", self.method(:on_handshake))
  
  if blk
    instance_eval(&blk)
  else
    setup_from_conf_file()
  end
  
  @token = self.get_token()
  if @token.nil? && (@token = self.get_token(false)).nil?
    log "Authentication failed. Check your password."
    self.disconnect(0)
  end
end

Instance Attribute Details

- (String) __cli_buffer (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the bot's command-line buffer



60
61
62
# File 'lib/bot.rb', line 60

def __cli_buffer
  @__cli_buffer
end

- (Array<Command>) _commands (readonly)



63
64
65
# File 'lib/bot.rb', line 63

def _commands
  @_commands
end

- (String) current_room



56
57
58
# File 'lib/bot.rb', line 56

def current_room
  @current_room
end

- (Boolean) logging (readonly)



75
76
77
# File 'lib/bot.rb', line 75

def logging
  @logging
end

- (Hash<Class, Object>) modules (readonly)



69
70
71
# File 'lib/bot.rb', line 69

def modules
  @modules
end

- (Hash<String, DA::Room>) rooms



50
51
52
# File 'lib/bot.rb', line 50

def rooms
  @rooms
end

- (TCPSocket) server (readonly)



66
67
68
# File 'lib/bot.rb', line 66

def server
  @server
end

- (Boolean) silent



53
54
55
# File 'lib/bot.rb', line 53

def silent
  @silent
end

- (String) token (readonly)



72
73
74
# File 'lib/bot.rb', line 72

def token
  @token
end

Instance Method Details

- (void) action(line, room)

This method returns an undefined value.

Send an action in room.



365
366
367
# File 'lib/bot.rb', line 365

def action line, room
  self.command "send", room, :body => "action main\n\n#{self.filter(line)}"
end

- (String) command(pktname, pktparam = nil, opts = {})

Send a command to the server

Options Hash (opts):

  • :body (String)

    the body of the packet to send



330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
# File 'lib/bot.rb', line 330

def command pktname, pktparam = nil, opts = {}
  str = ""
  str << pktname.to_s
  if pktparam
    str << " #{pktparam.to_s}"
  end
  opts.each{|k,v|
    next if k == :body
    str << "\n#{k}=#{v}"
  }
  if opts[:body]
    str << "\n\n"
    str << opts.delete(:body).to_s
  end
  str << "\n" unless pktname == "send"
  str << "\0"
  @server.write str
  str
end

- (OpenStruct) configuration(opts = {}) {|config| ... }

Access to bot configuration.

Options Hash (opts):

  • :nocallbacks (Boolean)

    prevent bot from running callbacks

Yields:

  • (config)

    Bot configuration



195
196
197
198
199
200
201
# File 'lib/bot.rb', line 195

def configuration opts = {}
  return @conf unless block_given?
  yield @conf
  return if opts[:nocallbacks]
  run_post_conf_callbacks
  log "Loaded configuration."
end

- (void) debug(*str)

This method returns an undefined value.

Debug messages.



161
162
163
164
# File 'lib/bot.rb', line 161

def debug *str
  str.map(&:to_s).each{|st|st.split("\n").each{|line|log "\e[1mdebug #\e[33m#{Time.now.to_i}\e[0m: #{line}"}}
  nil
end

- (void) disconnect(status)

This method returns an undefined value.

The clean way to disconnect from the server; finishes the logs, closes the database connection, disconnects gracefully from dAmn.



265
266
267
268
269
270
271
272
273
274
# File 'lib/bot.rb', line 265

def disconnect status
  log "Socket error! Forced exit." if status == 99
  Storage.close
  command "disconnect"
  log "Disconnected from server."
  log "participle shutting down."
  print "\e[1000D\e[K"
  flush_logs
  exit(status)
end

- (Array<Command>) each_command(key) {|command| ... }

When called in block format, just yields all the commands in order; otherwise, returns the appropriate commands array.

Yields:

  • (command)

    Iterates through each command [optional]



298
299
300
301
302
303
304
305
306
# File 'lib/bot.rb', line 298

def each_command key
  if block_given?
    @_commands[key].each do |cmd|
      yield cmd
    end
  else
    @_commands[key]
  end
end

- (String) filter(line)

Encode and HTML-escape a line. Ideally, should be run on all commands that send some kind of intended human readable data to the server.



380
381
382
# File 'lib/bot.rb', line 380

def filter(line)
  line.to_s.gsub(/([^ a-zA-Z0-9_.\-'",;!@#\$%^&\*\(\)\{\}\?\/<>=\+:])/u){"&##{$1.unpack("U*")[0]};"}
end

- (Object) kick(user, room, reason = nil)

Kick a user from room.



373
374
375
# File 'lib/bot.rb', line 373

def kick user, room, reason = nil
  self.command "kick", room, :u => user, :body => reason
end

- (Boolean) load_module(modname)

Adds all of a module's commands and adds an instance to the @modules hash.



237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/bot.rb', line 237

def load_module(modname)
  return false unless module_exists(modname)
  begin
    if @modules[Object.const_get(modname)]
      log "#{embolden(modname)} is already loaded."
      return false
    end
  rescue
  end
  begin
    load "ext/%s.rb" % modname.underscore
  rescue
    log "Uh-oh, couldn't load #{embolden(modname)}."
    raise
  end
  begin
    name = Object.const_get(modname)
    @modules[name] = name.new
    log "Loaded module #{embolden(name.to_s)}."
  rescue NameError
    return false
  end
  true
end

- (Boolean) module_exists(modname)

This merely checks if a properly named module file is found in ext/. The bot doesn't validate the file in any other way.



214
215
216
# File 'lib/bot.rb', line 214

def module_exists(modname)
  File.file?("ext/%s.rb" % modname.underscore)
end

- (Command) on(msgtype, pattern = nil, opts = {}, &blk)

Add a command to the hash.

Options Hash (opts):

  • :event (Symbol)

    Event on which to fire the command (defaults to a normal message)

  • :admin (Boolean)

    Whether only the bot's admin can use this command or not

  • :displayname (String)

    Controls the command name that shows up in the commands listing

  • :displaystr (String)

    Controls the appearance of the command when listed in help

  • :level (Fixnum)

    Minimum user level needed to use this command. Defaults to 1 (Guest)

  • :usage (String)

    Command usage

  • :klass (Class)

    The extension class (internal use)



319
320
321
322
# File 'lib/bot.rb', line 319

def on msgtype, pattern = nil, opts = {}, &blk
  options = {:trigger => true, :usage => nil, :admin => false, :klass => nil, :level => 1}.merge(opts)
  @_commands[msgtype] << Participle::Command.new(pattern, blk, options, @conf.trigger, self)
end

- (void) save_config

This method returns an undefined value.

Removes unnecessary keys and saves the current configuration to the config file. This method should always be used instead of attempting to write to the config file yourself.



285
286
287
288
289
290
291
292
# File 'lib/bot.rb', line 285

def save_config
  accepts = [:trigger, :blacklist, :logging, :pretty_display, :nick, :password, :admin, :channels, :extensions]
  items = Hash[@conf.marshal_dump.select{|k,v|accepts.include?(k)}]
  items.extensions.reject!{|a|%w[System Damn].include?(a)}.sort!
  open("conf/conf.yaml", "w") {|file|
    file.write(YAML.dump(items))
  }
end

- (void) say(line, room, parsed = true)

This method returns an undefined value.

Say something in room.



355
356
357
358
359
# File 'lib/bot.rb', line 355

def say line, room, parsed = true
  np = parsed ? "" : "np"
  line = @conf.chromacity_name && @conf.chromacity_line ? "#{line} <abbr title='colors:#{@conf.chromacity_name.upcase}:#{@conf.chromacity_line.upcase}'></abbr>" : line
  self.command "send", DA::Room === room ? room.name_formatted : room, :body => "#{np}msg main\n\n%s" % self.filter(line)
end

- (Object) set_configuration_option(key, value)

Simple frontend method for editing the bot's configuration.



207
208
209
# File 'lib/bot.rb', line 207

def set_configuration_option key, value
  @conf.send("#{key}=".to_sym, value)
end

- (void) start!

This method returns an undefined value.

Starts the bot.



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/bot.rb', line 168

def start!
  if $opts[:prompt] && RUBY_PLATFORM =~ /darwin|linux/
    @__cli_commands = {}
    @__cli_tabcomplete = {}
    @__cli_buffer = ""
    @__cli_history = []
    self.cli_init
    self.threaded_prompt
  end
  @conf.start_time = DateTime.now
  log "Connecting to dAmn..."
  begin
    command "dAmnClient", 0.3, :agent => "participle/beta #{@conf.version}"
    loop {
      self.read_buffer
    }
  rescue SystemExit => e
    self.disconnect(e.status)
  rescue Errno::EPIPE, Errno::ECONNRESET
    self.reconnect
  end
end

- (Boolean) unload_module(modname)

Removes all the commands of the named module and deletes its instance.



221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/bot.rb', line 221

def unload_module(modname)
  @_commands.keys.each{|key|
    @_commands[key].delete_if{|x|x.opts[:klass].to_s == modname}
  }
  log "Unloaded module #{embolden(modname)}."
  begin
    @modules.delete(Object.const_get(modname))
  rescue NameError
    return false
  end
  true
end

- (Object) wait_for(msgtype, opts = {}) {|Packet| ... }

Executes a block when a packet meeting certain requirements is found.

Examples:

Basic example

msg.reply("Ping?")
wait_for("recv#msg", :user => self.configuration.nick, :room => msg.room, :body => "Ping?") do |pkt|
  msg.reply("Pong!")
end

Options Hash (opts):

  • :room (String, DA::Room)

    the room to watch

  • :user (String, DA::User)

    the user to watch

  • :body (String, Regexp)

    the message to expect

Yields:



145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/bot.rb', line 145

def wait_for msgtype, opts = {}, &blk
  options = ({:user => nil, :room => nil, :body => nil}).merge(opts)
  bind_event(msgtype) do |pkt|
    if (options[:user].nil? || options[:user] === pkt.subpkt[:from] || options[:user] === pkt.param.sub("login:", "")) &&
       (options[:room].nil? || options[:room] === pkt.room) &&
       (options[:body].nil? || options[:body] === unchrome(pkt.body))
       
      blk.call(pkt)
      self.unbind_last(msgtype)
    end
  end
end

- (Fixnum) write(data)

Write some data to the server.



279
280
281
# File 'lib/bot.rb', line 279

def write(data)
  @server.write(data)
end