@yield
# File lib/cinch/bot.rb, line 480 def initialize(&b) @logger = Logger::FormattedLogger.new($stderr) @events = {} @config = OpenStruct.new({ :server => "localhost", :port => 6667, :ssl => OpenStruct.new({ :use => false, :verify => false, :client_cert => nil, :ca_path => "/etc/ssl/certs", }), :password => nil, :nick => "cinch", :nicks => nil, :realname => "cinch", :user => "cinch", :verbose => true, :messages_per_second => 0.5, :server_queue_size => 10, :strictness => :forgiving, :message_split_start => '... ', :message_split_end => ' ...', :max_messages => nil, :plugins => OpenStruct.new({ :plugins => [], :prefix => /^!/, :suffix => nil, :options => Hash.new {|h,k| h[k] = {}}, }), :channels => [], :encoding => :irc, :reconnect => true, :local_host => nil, :timeouts => OpenStruct.new({ :read => 240, :connect => 10, }), :ping_interval => 120, }) @semaphores_mutex = Mutex.new @semaphores = Hash.new { |h,k| h[k] = Mutex.new } @plugins = [] @callback = Callback.new(self) @channels = [] @handler_threads = [] @quitting = false @user_manager = UserManager.new(self) @channel_manager = ChannelManager.new(self) instance_eval(&b) if block_given? on :connect do bot.config.channels.each do |channel| bot.join channel end end end
Helper method for turning a String into a {Channel} object.
@param [String] channel a channel name @return [Channel] a {Channel} object @example
on :message, /^please join (#.+)$/ do |m, target| Channel(target).join end
# File lib/cinch/bot.rb, line 79 def Channel(channel) return channel if channel.is_a?(Channel) @channel_manager.find_ensured(channel) end
Helper method for turning a String into an {User} object.
@param [String] user a user's nickname @return [User] an {User} object @example
on :message, /^tell me everything about (.+)$/ do |m, target| user = User(target) m.reply "%s is named %s and connects from %s" % [user.nick, user.name, user.host] end
# File lib/cinch/bot.rb, line 93 def User(user) return user if user.is_a?(User) @user_manager.find_ensured(user) end
Invoke an action (/me) in/to a recipient (a channel or user). You should be using {Channel#action} and {User#action} instead.
@param [String] recipient the recipient @param [String] text the message to send @return [void] @see Channel#action @see User#action @see safe_action
# File lib/cinch/bot.rb, line 261 def action(recipient, text) raw("PRIVMSG #{recipient} :\0001ACTION #{text}\0001") end
This method is used to set a bot's options. It indeed does nothing else but yielding {Bot#config}, but it makes for a nice DSL.
@yieldparam [Struct] config the bot's config @return [void]
# File lib/cinch/bot.rb, line 382 def configure(&block) @callback.instance_exec(@config, &block) end
(see Logger::Logger#debug) @see Logger::Logger#debug
# File lib/cinch/bot.rb, line 469 def debug(msg) @logger.debug(msg) end
@param [Symbol] event The event type @param [Message, nil] msg The message which is responsible for
and attached to the event, or nil.
@param [Array] *arguments A list of additional arguments to pass
to event handlers
@return [void]
# File lib/cinch/bot.rb, line 337 def dispatch(event, msg = nil, *arguments) if handlers = find(event, msg) handlers.each do |handler| regexps, args, block = *handler # calling Message#match multiple times is not a problem # because we cache the result if msg regexp = regexps.find { |rx| msg.match(rx.to_r(msg), event) } captures = msg.match(regexp.to_r(msg), event).captures else captures = [] end invoke(block, args, msg, captures, arguments) end end end
Try to create a free nick, first by cycling through all available alternatives and then by appending underscores.
@param [String] base The base nick to start trying from @api private @return String
# File lib/cinch/bot.rb, line 571 def generate_next_nick(base = nil) nicks = @config.nicks || [] if base # if `base` is not in our list of nicks to try, assume that it's # custom and just append an underscore if !nicks.include?(base) return base + "_" else # if we have a base, try the next nick or append an # underscore if no more nicks are left new_index = nicks.index(base) + 1 if nicks[new_index] return nicks[new_index] else return base + "_" end end else # if we have no base, try the first possible nick new_nick = @config.nicks ? @config.nicks.first : @config.nick end end
Stop execution of the current {on} handler.
@return [void]
# File lib/cinch/bot.rb, line 147 def halt throw :halt end
Define helper methods in the context of the bot.
@yield Expects a block containing method definitions @return [void]
# File lib/cinch/bot.rb, line 102 def helpers(&b) Callback.class_eval(&b) end
Join a channel.
@param [String, Channel] channel either the name of a channel or a {Channel} object @param [String] key optionally the key of the channel @return [void] @see Channel#join
# File lib/cinch/bot.rb, line 451 def join(channel, key = nil) Channel(channel).join(key) end
Sends a PRIVMSG to a recipient (a channel or user). You should be using {Channel#send} and {User#send} instead.
@param [String] recipient the recipient @param [String] text the message to send @param [Boolean] notice Use NOTICE instead of PRIVMSG? @return [void] @see Channel#send @see User#send @see safe_msg
# File lib/cinch/bot.rb, line 173 def msg(recipient, text, notice = false) text = text.to_s split_start = @config.message_split_start || "" split_end = @config.message_split_end || "" command = notice ? "NOTICE" : "PRIVMSG" text.split(/\r\n|\r|\n/).each do |line| maxlength = 510 - (":" + " #{command} " + " :").size maxlength = maxlength - self.mask.to_s.length - recipient.to_s.length maxlength_without_end = maxlength - split_end.bytesize if line.bytesize > maxlength splitted = [] while line.bytesize > maxlength_without_end pos = line.rindex(/\s/, maxlength_without_end) r = pos || maxlength_without_end splitted << line.slice!(0, r) + split_end.tr(" ", "\u00A0") line = split_start.tr(" ", "\u00A0") + line.lstrip end splitted << line splitted[0, (@config.max_messages || splitted.size)].each do |string| string.tr!("\u00A0", " ") # clean string from any non-breaking spaces raw("#{command} #{recipient} :#{string}") end else raw("#{command} #{recipient} :#{line}") end end end
# File lib/cinch/bot.rb, line 557 def nick=(new_nick) if new_nick.size > @irc.isupport["NICKLEN"] && strict? raise Exceptions::NickTooLong, new_nick end @config.nick = new_nick raw "NICK #{new_nick}" end
Sends a NOTICE to a recipient (a channel or user). You should be using {Channel#notice} and {User#notice} instead.
@param [String] recipient the recipient @param [String] text the message to send @return [void] @see Channel#notice @see User#notice @see safe_notice
# File lib/cinch/bot.rb, line 216 def notice(recipient, text) msg(recipient, text, true) end
Registers a handler.
@param [String, Symbol, Integer] event the event to match. Available
events are all IRC commands in lowercase as symbols, all numeric replies, and the following: - :channel (a channel message) - :private (a private message) - :message (both channel and private messages) - :error (handling errors, use a numeric error code as `match`) - :ctcp (ctcp requests, use a ctcp command as `match`)
@param [Regexp, String, Integer] match every message of the
right event will be checked against this argument and the event will only be called if it matches
@yieldparam [String] *args each capture group of the regex will
be one argument to the block. It is optional to accept them, though
@return [void]
# File lib/cinch/bot.rb, line 306 def on(event, regexps = [], *args, &block) regexps = [*regexps] regexps = [//] if regexps.empty? event = event.to_sym regexps.map! do |regexp| pattern = case regexp when Pattern regexp when Regexp Pattern.new(nil, regexp, nil) else if event == :ctcp Pattern.new(/^/, /#{Regexp.escape(regexp.to_s)}(?:$| .+)/, nil) else Pattern.new(/^/, /#{Regexp.escape(regexp.to_s)}/, /$/) end end debug "[on handler] Registering handler with pattern `#{pattern.inspect}`, reacting on `#{event}`" pattern end (@events[event] ||= []) << [regexps, args, block] end
Part a channel.
@param [String, Channel] channel either the name of a channel or a {Channel} object @param [String] reason an optional reason/part message @return [void] @see Channel#part
# File lib/cinch/bot.rb, line 461 def part(channel, reason = nil) Channel(channel).part(reason) end
Disconnects from the server.
@param [String] message The quit message to send while quitting @return [void]
# File lib/cinch/bot.rb, line 390 def quit(message = nil) @quitting = true command = message ? "QUIT :#{message}" : "QUIT" raw command end
Sends a raw message to the server.
@param [String] command The message to send. @return [void] @see IRC#message
# File lib/cinch/bot.rb, line 159 def raw(command) @irc.message(command) end
Registers a plugin.
@param [Class<Plugin>] plugin The plugin class to register @return [void]
# File lib/cinch/bot.rb, line 370 def register_plugin(plugin) @plugins << plugin.new(self) end
Register all plugins from `@config.plugins.plugins`.
@return [void]
# File lib/cinch/bot.rb, line 360 def register_plugins @config.plugins.plugins.each do |plugin| register_plugin(plugin) end end
Like {action}, but remove any non-printable characters from `text`. The purpose of this method is to send text from untrusted sources, like other users or feeds.
Note: this will *break* any mIRC color codes embedded in the string.
@param (see action) @return (see action) @see action @see Channel#safe_action @see User#safe_action @todo Handle mIRC color codes more gracefully.
# File lib/cinch/bot.rb, line 278 def safe_action(recipient, text) action(recipient, Cinch.filter_string(text)) end
Like {msg}, but remove any non-printable characters from `text`. The purpose of this method is to send text of untrusted sources, like other users or feeds.
Note: this will *break* any mIRC color codes embedded in the string.
@return (see msg) @param (see msg) @see msg @see User#safe_send @see Channel#safe_send @todo Handle mIRC color codes more gracefully.
# File lib/cinch/bot.rb, line 233 def safe_msg(recipient, text) msg(recipient, Cinch.filter_string(text)) end
Like {safe_msg} but for notices.
@return (see safe_msg) @param (see safe_msg) @see safe_notice @see notice @see User#safe_notice @see Channel#safe_notice @todo (see safe_msg)
# File lib/cinch/bot.rb, line 248 def safe_notice(recipient, text) msg(recipient, Cinch.filter_string(text), true) end
@return [Boolean] True if the bot is using SSL to connect to the
server.
# File lib/cinch/bot.rb, line 597 def secure? @config[:ssl] == true || (@config[:ssl].is_a?(Hash) && @config[:ssl][:use]) end
Connects the bot to a server.
@param [Boolean] plugins Automatically register plugins from
`@config.plugins.plugins`?
@return [void] Connects the bot to a server.
@param [Boolean] plugins Automatically register plugins from
`@config.plugins.plugins`?
@return [void]
# File lib/cinch/bot.rb, line 406 def start(plugins = true) @reconnects = 0 register_plugins if plugins begin @user_manager.each do |user| user.in_whois = false user.unsync_all end # reset state of all users @channel_manager.each do |channel| channel.unsync_all end # reset state of all channels @logger.debug "Connecting to #{@config.server}:#{@config.port}" @irc = IRC.new(self) @irc.connect if @config.reconnect && !@quitting # double the delay for each unsuccesful reconnection attempt if @last_connection_was_successful @reconnects = 0 @last_connection_was_successful = false else @reconnects += 1 end # Sleep for a few seconds before reconnecting to prevent being # throttled by the IRC server wait = 2**@reconnects @logger.debug "Waiting #{wait} seconds before reconnecting" sleep wait end end while @config.reconnect and not @quitting end
@return [Boolean] True if the bot reports ISUPPORT violations as
exceptions.
# File lib/cinch/bot.rb, line 475 def strict? @config.strictness == :strict end
Since Cinch uses threads, all handlers can be run simultaneously, even the same handler multiple times. This also means, that your code has to be thread-safe. Most of the time, this is not a problem, but if you are accessing stored data, you will most likely have to synchronize access to it. Instead of managing all mutexes yourself, Cinch provides a synchronize method, which takes a name and block.
Synchronize blocks with the same name share the same mutex, which means that only one of them will be executed at a time.
@param [String, Symbol] name a name for the synchronize block. @return [void] @yield
@example
configure do |c| … @i = 0 end on :channel, /^start counting!/ do synchronize(:my_counter) do 10.times do val = @i # at this point, another thread might've incremented :i already. # this thread wouldn't know about it, though. @i = val + 1 end end end
# File lib/cinch/bot.rb, line 137 def synchronize(name, &block) # Must run the default block +/ fetch in a thread safe way in order to # ensure we always get the same mutex for a given name. semaphore = @semaphores_mutex.synchronize { @semaphores[name] } semaphore.synchronize(&block) end
This method is only provided in order to give Bot and User a common interface.
@return [false] Always returns `false`. @see User#unknown? See User#unknown? for the method's real use.
# File lib/cinch/bot.rb, line 606 def unknown? false end
Generated with the Darkfish Rdoc Generator 2.