grinch 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.yardopts +1 -0
- data/LICENSE +22 -0
- data/README.md +180 -0
- data/docs/bot_options.md +454 -0
- data/docs/changes.md +541 -0
- data/docs/common_mistakes.md +60 -0
- data/docs/common_tasks.md +57 -0
- data/docs/encodings.md +69 -0
- data/docs/events.md +273 -0
- data/docs/getting_started.md +184 -0
- data/docs/logging.md +90 -0
- data/docs/migrating.md +267 -0
- data/docs/plugins.md +4 -0
- data/docs/readme.md +20 -0
- data/examples/basic/autovoice.rb +32 -0
- data/examples/basic/google.rb +35 -0
- data/examples/basic/hello.rb +15 -0
- data/examples/basic/join_part.rb +34 -0
- data/examples/basic/memo.rb +39 -0
- data/examples/basic/msg.rb +16 -0
- data/examples/basic/seen.rb +36 -0
- data/examples/basic/urban_dict.rb +35 -0
- data/examples/basic/url_shorten.rb +35 -0
- data/examples/plugins/autovoice.rb +37 -0
- data/examples/plugins/custom_prefix.rb +23 -0
- data/examples/plugins/dice_roll.rb +38 -0
- data/examples/plugins/google.rb +36 -0
- data/examples/plugins/hello.rb +22 -0
- data/examples/plugins/hooks.rb +36 -0
- data/examples/plugins/join_part.rb +42 -0
- data/examples/plugins/lambdas.rb +35 -0
- data/examples/plugins/last_nick.rb +24 -0
- data/examples/plugins/msg.rb +22 -0
- data/examples/plugins/multiple_matches.rb +32 -0
- data/examples/plugins/own_events.rb +37 -0
- data/examples/plugins/seen.rb +45 -0
- data/examples/plugins/timer.rb +22 -0
- data/examples/plugins/url_shorten.rb +33 -0
- data/lib/cinch.rb +5 -0
- data/lib/cinch/ban.rb +50 -0
- data/lib/cinch/bot.rb +479 -0
- data/lib/cinch/cached_list.rb +19 -0
- data/lib/cinch/callback.rb +20 -0
- data/lib/cinch/channel.rb +463 -0
- data/lib/cinch/channel_list.rb +29 -0
- data/lib/cinch/configuration.rb +73 -0
- data/lib/cinch/configuration/bot.rb +48 -0
- data/lib/cinch/configuration/dcc.rb +16 -0
- data/lib/cinch/configuration/plugins.rb +41 -0
- data/lib/cinch/configuration/sasl.rb +19 -0
- data/lib/cinch/configuration/ssl.rb +19 -0
- data/lib/cinch/configuration/timeouts.rb +14 -0
- data/lib/cinch/constants.rb +533 -0
- data/lib/cinch/dcc.rb +12 -0
- data/lib/cinch/dcc/dccable_object.rb +37 -0
- data/lib/cinch/dcc/incoming.rb +1 -0
- data/lib/cinch/dcc/incoming/send.rb +147 -0
- data/lib/cinch/dcc/outgoing.rb +1 -0
- data/lib/cinch/dcc/outgoing/send.rb +122 -0
- data/lib/cinch/exceptions.rb +46 -0
- data/lib/cinch/formatting.rb +125 -0
- data/lib/cinch/handler.rb +118 -0
- data/lib/cinch/handler_list.rb +90 -0
- data/lib/cinch/helpers.rb +231 -0
- data/lib/cinch/irc.rb +924 -0
- data/lib/cinch/isupport.rb +98 -0
- data/lib/cinch/log_filter.rb +21 -0
- data/lib/cinch/logger.rb +168 -0
- data/lib/cinch/logger/formatted_logger.rb +97 -0
- data/lib/cinch/logger/zcbot_logger.rb +22 -0
- data/lib/cinch/logger_list.rb +85 -0
- data/lib/cinch/mask.rb +69 -0
- data/lib/cinch/message.rb +392 -0
- data/lib/cinch/message_queue.rb +107 -0
- data/lib/cinch/mode_parser.rb +76 -0
- data/lib/cinch/network.rb +104 -0
- data/lib/cinch/open_ended_queue.rb +26 -0
- data/lib/cinch/pattern.rb +65 -0
- data/lib/cinch/plugin.rb +515 -0
- data/lib/cinch/plugin_list.rb +38 -0
- data/lib/cinch/rubyext/float.rb +3 -0
- data/lib/cinch/rubyext/module.rb +26 -0
- data/lib/cinch/rubyext/string.rb +33 -0
- data/lib/cinch/sasl.rb +34 -0
- data/lib/cinch/sasl/dh_blowfish.rb +71 -0
- data/lib/cinch/sasl/diffie_hellman.rb +47 -0
- data/lib/cinch/sasl/mechanism.rb +6 -0
- data/lib/cinch/sasl/plain.rb +26 -0
- data/lib/cinch/syncable.rb +83 -0
- data/lib/cinch/target.rb +199 -0
- data/lib/cinch/timer.rb +145 -0
- data/lib/cinch/user.rb +488 -0
- data/lib/cinch/user_list.rb +87 -0
- data/lib/cinch/utilities/deprecation.rb +16 -0
- data/lib/cinch/utilities/encoding.rb +37 -0
- data/lib/cinch/utilities/kernel.rb +13 -0
- data/lib/cinch/version.rb +4 -0
- metadata +140 -0
    
        data/lib/cinch/mask.rb
    ADDED
    
    | @@ -0,0 +1,69 @@ | |
| 1 | 
            +
            module Cinch
         | 
| 2 | 
            +
              # This class represents masks, which are primarily used for bans.
         | 
| 3 | 
            +
              class Mask
         | 
| 4 | 
            +
                # @return [String]
         | 
| 5 | 
            +
                attr_reader :nick
         | 
| 6 | 
            +
                # @return [String]
         | 
| 7 | 
            +
                attr_reader :user
         | 
| 8 | 
            +
                # @return [String]
         | 
| 9 | 
            +
                attr_reader :host
         | 
| 10 | 
            +
                # @return [String]
         | 
| 11 | 
            +
                attr_reader :mask
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                # @version 1.1.2
         | 
| 14 | 
            +
                # @param [String] mask
         | 
| 15 | 
            +
                def initialize(mask)
         | 
| 16 | 
            +
                  @mask = mask
         | 
| 17 | 
            +
                  @nick, @user, @host = mask.match(/(.+)!(.+)@(.+)/)[1..-1]
         | 
| 18 | 
            +
                  @regexp = Regexp.new("^" + Regexp.escape(mask).gsub("\\*", ".*").gsub("\\?", ".?") + "$")
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                # @return [Boolean]
         | 
| 22 | 
            +
                # @since 1.1.0
         | 
| 23 | 
            +
                def ==(other)
         | 
| 24 | 
            +
                  other.respond_to?(:mask) && other.mask == @mask
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                # @return [Boolean]
         | 
| 28 | 
            +
                # @since 1.1.0
         | 
| 29 | 
            +
                def eql?(other)
         | 
| 30 | 
            +
                  other.is_a?(self.class) && self == other
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                # @return [Fixnum]
         | 
| 34 | 
            +
                def hash
         | 
| 35 | 
            +
                  @mask.hash
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                # @param [Mask, String, #mask] target
         | 
| 39 | 
            +
                # @return [Boolean]
         | 
| 40 | 
            +
                # @version 1.1.2
         | 
| 41 | 
            +
                def match(target)
         | 
| 42 | 
            +
                  return self.class.from(target).mask =~ @regexp
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  # TODO support CIDR (freenode)
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
                alias_method :=~, :match
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                # @return [String]
         | 
| 49 | 
            +
                def to_s
         | 
| 50 | 
            +
                  @mask.dup
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                # @param [String, #mask] target
         | 
| 54 | 
            +
                # @return [target] if already a Mask
         | 
| 55 | 
            +
                # @return [Mask]
         | 
| 56 | 
            +
                # @version 2.0.0
         | 
| 57 | 
            +
                def self.from(target)
         | 
| 58 | 
            +
                  return target if target.is_a?(self)
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  if target.respond_to?(:mask)
         | 
| 61 | 
            +
                    mask = target.mask
         | 
| 62 | 
            +
                  else
         | 
| 63 | 
            +
                    mask = Mask.new(target.to_s)
         | 
| 64 | 
            +
                  end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                  return mask
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
              end
         | 
| 69 | 
            +
            end
         | 
| @@ -0,0 +1,392 @@ | |
| 1 | 
            +
            # -*- coding: utf-8 -*-
         | 
| 2 | 
            +
            require "time"
         | 
| 3 | 
            +
            require "cinch/formatting"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Cinch
         | 
| 6 | 
            +
              # This class serves two purposes. For one, it simply
         | 
| 7 | 
            +
              # represents incoming messages and allows for querying various
         | 
| 8 | 
            +
              # details (who sent the message, what kind of message it is, etc).
         | 
| 9 | 
            +
              #
         | 
| 10 | 
            +
              # At the same time, it allows **responding** to messages, which
         | 
| 11 | 
            +
              # means sending messages to either users or channels.
         | 
| 12 | 
            +
              class Message
         | 
| 13 | 
            +
                # @return [String]
         | 
| 14 | 
            +
                attr_reader :raw
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                # @return [String]
         | 
| 17 | 
            +
                attr_reader :prefix
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                # @return [String]
         | 
| 20 | 
            +
                attr_reader :command
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                # @return [Array<String>]
         | 
| 23 | 
            +
                attr_reader :params
         | 
| 24 | 
            +
                
         | 
| 25 | 
            +
                # @return [Hash]
         | 
| 26 | 
            +
                attr_reader :tags
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                # @return [Array<Symbol>]
         | 
| 29 | 
            +
                attr_reader :events
         | 
| 30 | 
            +
                # @api private
         | 
| 31 | 
            +
                attr_writer :events
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                # @return [Time]
         | 
| 34 | 
            +
                # @since 2.0.0
         | 
| 35 | 
            +
                attr_reader :time
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                # @return [Bot]
         | 
| 38 | 
            +
                # @since 1.1.0
         | 
| 39 | 
            +
                attr_reader :bot
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                # @return [User] The user who sent this message
         | 
| 42 | 
            +
                attr_reader :user
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                # @return [String, nil]
         | 
| 45 | 
            +
                attr_reader :server
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                # @return [Integer, nil] the numeric error code, if any
         | 
| 48 | 
            +
                attr_reader :error
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                # @return [String, nil] the command part of an CTCP message
         | 
| 51 | 
            +
                attr_reader :ctcp_command
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                # @return [Channel] The channel in which this message was sent
         | 
| 54 | 
            +
                attr_reader :channel
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                # @return [String, nil] the CTCP message, without \001 control characters
         | 
| 57 | 
            +
                attr_reader :ctcp_message
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                # @return [Array<String>, nil]
         | 
| 60 | 
            +
                attr_reader :ctcp_args
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                # @return [String, nil]
         | 
| 63 | 
            +
                attr_reader :message
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                # @return [String, nil] The action message
         | 
| 66 | 
            +
                # @since 2.0.0
         | 
| 67 | 
            +
                attr_reader :action_message
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                # @return [Target]
         | 
| 70 | 
            +
                attr_reader :target
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                # The STATUSMSG mode a channel message was sent to.
         | 
| 73 | 
            +
                #
         | 
| 74 | 
            +
                # Some IRC servers allow sending messages limited to people in a
         | 
| 75 | 
            +
                # channel who have a certain mode. For example, by sending a
         | 
| 76 | 
            +
                # message to `+#channel`, only people who are voiced, or have a
         | 
| 77 | 
            +
                # higher mode (op) will receive the message.
         | 
| 78 | 
            +
                #
         | 
| 79 | 
            +
                # This attribute contains the mode character the message was sent
         | 
| 80 | 
            +
                # to, or nil if it was a normal message. For the previous example,
         | 
| 81 | 
            +
                # this attribute would be set to `"v"`, for voiced.
         | 
| 82 | 
            +
                #
         | 
| 83 | 
            +
                # @return [String, nil]
         | 
| 84 | 
            +
                # @since 2.3.0
         | 
| 85 | 
            +
                attr_reader :statusmsg_mode
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                def initialize(msg, bot)
         | 
| 88 | 
            +
                  @raw     = msg
         | 
| 89 | 
            +
                  @bot     = bot
         | 
| 90 | 
            +
                  @matches = {:ctcp => {}, :action => {}, :other => {}}
         | 
| 91 | 
            +
                  @events  = []
         | 
| 92 | 
            +
                  @time    = Time.now
         | 
| 93 | 
            +
                  @statusmsg_mode = nil
         | 
| 94 | 
            +
                  parse if msg
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                # @api private
         | 
| 98 | 
            +
                # @return [void]
         | 
| 99 | 
            +
                def parse
         | 
| 100 | 
            +
                  match = @raw.match(/(?:^@([^:]+))?(?::?(\S+) )?(\S+)(.*)/)
         | 
| 101 | 
            +
                  tags, @prefix, @command, raw_params = match.captures
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                  if @bot.irc.network.ngametv?
         | 
| 104 | 
            +
                    if @prefix != "ngame"
         | 
| 105 | 
            +
                      @prefix = "%s!%s@%s" % [@prefix, @prefix, @prefix]
         | 
| 106 | 
            +
                    end
         | 
| 107 | 
            +
                  end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                  @params  = parse_params(raw_params)
         | 
| 110 | 
            +
                  @tags    = parse_tags(tags)
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                  @user    = parse_user
         | 
| 113 | 
            +
                  @channel, @statusmsg_mode = parse_channel
         | 
| 114 | 
            +
                  @target  = @channel || @user
         | 
| 115 | 
            +
                  @server  = parse_server
         | 
| 116 | 
            +
                  @error   = parse_error
         | 
| 117 | 
            +
                  @message = parse_message
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                  @ctcp_message = parse_ctcp_message
         | 
| 120 | 
            +
                  @ctcp_command = parse_ctcp_command
         | 
| 121 | 
            +
                  @ctcp_args    = parse_ctcp_args
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                  @action_message = parse_action_message
         | 
| 124 | 
            +
                end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                # @group Type checking
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                # @return [Boolean] true if the message is an numeric reply (as
         | 
| 129 | 
            +
                #   opposed to a command)
         | 
| 130 | 
            +
                def numeric_reply?
         | 
| 131 | 
            +
                  !!@command.match(/^\d{3}$/)
         | 
| 132 | 
            +
                end
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                # @return [Boolean] true if the message describes an error
         | 
| 135 | 
            +
                def error?
         | 
| 136 | 
            +
                  !@error.nil?
         | 
| 137 | 
            +
                end
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                # @return [Boolean] true if this message was sent in a channel
         | 
| 140 | 
            +
                def channel?
         | 
| 141 | 
            +
                  !@channel.nil?
         | 
| 142 | 
            +
                end
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                # @return [Boolean] true if the message is an CTCP message
         | 
| 145 | 
            +
                def ctcp?
         | 
| 146 | 
            +
                  !!(@params.last =~ /\001.+\001/)
         | 
| 147 | 
            +
                end
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                # @return [Boolean] true if the message is an action (/me)
         | 
| 150 | 
            +
                # @since 2.0.0
         | 
| 151 | 
            +
                def action?
         | 
| 152 | 
            +
                  @ctcp_command == "ACTION"
         | 
| 153 | 
            +
                end
         | 
| 154 | 
            +
             | 
| 155 | 
            +
                # @endgroup
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                # @api private
         | 
| 158 | 
            +
                # @return [MatchData]
         | 
| 159 | 
            +
                def match(regexp, type, strip_colors)
         | 
| 160 | 
            +
                  text = ""
         | 
| 161 | 
            +
                  case type
         | 
| 162 | 
            +
                  when :ctcp
         | 
| 163 | 
            +
                    text = ctcp_message
         | 
| 164 | 
            +
                  when :action
         | 
| 165 | 
            +
                    text = action_message
         | 
| 166 | 
            +
                  else
         | 
| 167 | 
            +
                    text = message.to_s
         | 
| 168 | 
            +
                    type = :other
         | 
| 169 | 
            +
                  end
         | 
| 170 | 
            +
             | 
| 171 | 
            +
                  if strip_colors
         | 
| 172 | 
            +
                    text = Cinch::Formatting.unformat(text)
         | 
| 173 | 
            +
                  end
         | 
| 174 | 
            +
             | 
| 175 | 
            +
                  @matches[type][regexp] ||= text.match(regexp)
         | 
| 176 | 
            +
                end
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                # @group Replying
         | 
| 179 | 
            +
             | 
| 180 | 
            +
                # Replies to a message, automatically determining if it was a
         | 
| 181 | 
            +
                # channel or a private message.
         | 
| 182 | 
            +
                #
         | 
| 183 | 
            +
                # If the message is a STATUSMSG, i.e. it was send to `+#channel`
         | 
| 184 | 
            +
                # or `@#channel` instead of `#channel`, the reply will be sent as
         | 
| 185 | 
            +
                # the same kind of STATUSMSG. See {#statusmsg_mode} for more
         | 
| 186 | 
            +
                # information on STATUSMSG.
         | 
| 187 | 
            +
                #
         | 
| 188 | 
            +
                # @param [String] text the message
         | 
| 189 | 
            +
                # @param [Boolean] prefix if prefix is true and the message was in
         | 
| 190 | 
            +
                #   a channel, the reply will be prefixed by the nickname of whoever
         | 
| 191 | 
            +
                #   send the mesage
         | 
| 192 | 
            +
                # @return [void]
         | 
| 193 | 
            +
                def reply(text, prefix = false)
         | 
| 194 | 
            +
                  text = text.to_s
         | 
| 195 | 
            +
                  if @channel && prefix
         | 
| 196 | 
            +
                    text = text.split("\n").map {|l| "#{user.nick}: #{l}"}.join("\n")
         | 
| 197 | 
            +
                  end
         | 
| 198 | 
            +
             | 
| 199 | 
            +
                  reply_target.send(text)
         | 
| 200 | 
            +
                end
         | 
| 201 | 
            +
             | 
| 202 | 
            +
                # Like {#reply}, but using {Target#safe_send} instead
         | 
| 203 | 
            +
                #
         | 
| 204 | 
            +
                # @param (see #reply)
         | 
| 205 | 
            +
                # @return (see #reply)
         | 
| 206 | 
            +
                def safe_reply(text, prefix = false)
         | 
| 207 | 
            +
                  text = text.to_s
         | 
| 208 | 
            +
                  if channel && prefix
         | 
| 209 | 
            +
                    text = "#{@user.nick}: #{text}"
         | 
| 210 | 
            +
                  end
         | 
| 211 | 
            +
                  reply_target.safe_send(text)
         | 
| 212 | 
            +
                end
         | 
| 213 | 
            +
             | 
| 214 | 
            +
                # Reply to a message with an action.
         | 
| 215 | 
            +
                #
         | 
| 216 | 
            +
                # For its behaviour with regard to STATUSMSG, see {#reply}.
         | 
| 217 | 
            +
                #
         | 
| 218 | 
            +
                # @param [String] text the action message
         | 
| 219 | 
            +
                # @return [void]
         | 
| 220 | 
            +
                def action_reply(text)
         | 
| 221 | 
            +
                  text = text.to_s
         | 
| 222 | 
            +
                  reply_target.action(text)
         | 
| 223 | 
            +
                end
         | 
| 224 | 
            +
             | 
| 225 | 
            +
                # Like {#action_reply}, but using {Target#safe_action} instead
         | 
| 226 | 
            +
                #
         | 
| 227 | 
            +
                # @param (see #action_reply)
         | 
| 228 | 
            +
                # @return (see #action_reply)
         | 
| 229 | 
            +
                def safe_action_reply(text)
         | 
| 230 | 
            +
                  text = text.to_s
         | 
| 231 | 
            +
                  reply_target.safe_action(text)
         | 
| 232 | 
            +
                end
         | 
| 233 | 
            +
             | 
| 234 | 
            +
                # Reply to a CTCP message
         | 
| 235 | 
            +
                #
         | 
| 236 | 
            +
                # @return [void]
         | 
| 237 | 
            +
                def ctcp_reply(answer)
         | 
| 238 | 
            +
                  return unless ctcp?
         | 
| 239 | 
            +
                  @user.notice "\001#{@ctcp_command} #{answer}\001"
         | 
| 240 | 
            +
                end
         | 
| 241 | 
            +
             | 
| 242 | 
            +
                # @endgroup
         | 
| 243 | 
            +
             | 
| 244 | 
            +
                # @return [String]
         | 
| 245 | 
            +
                # @since 1.1.0
         | 
| 246 | 
            +
                def to_s
         | 
| 247 | 
            +
                  "#<Cinch::Message @raw=#{@raw.chomp.inspect} @params=#{@params.inspect} channel=#{@channel.inspect} user=#{@user.inspect}>"
         | 
| 248 | 
            +
                end
         | 
| 249 | 
            +
             | 
| 250 | 
            +
                private
         | 
| 251 | 
            +
                def reply_target
         | 
| 252 | 
            +
                  if @channel.nil? || @statusmsg_mode.nil?
         | 
| 253 | 
            +
                    return @target
         | 
| 254 | 
            +
                  end
         | 
| 255 | 
            +
                  prefix = @bot.irc.isupport["PREFIX"][@statusmsg_mode]
         | 
| 256 | 
            +
                  return Target.new(prefix + @channel.name, @bot)
         | 
| 257 | 
            +
                end
         | 
| 258 | 
            +
                def regular_command?
         | 
| 259 | 
            +
                  !numeric_reply? # a command can only be numeric or "regular"…
         | 
| 260 | 
            +
                end
         | 
| 261 | 
            +
             | 
| 262 | 
            +
                def parse_params(raw_params)
         | 
| 263 | 
            +
                  params     = []
         | 
| 264 | 
            +
                  if match = raw_params.match(/(?:^:| :)(.*)$/)
         | 
| 265 | 
            +
                    params = match.pre_match.split(" ")
         | 
| 266 | 
            +
                    params << match[1]
         | 
| 267 | 
            +
                  else
         | 
| 268 | 
            +
                    params = raw_params.split(" ")
         | 
| 269 | 
            +
                  end
         | 
| 270 | 
            +
             | 
| 271 | 
            +
                  return params
         | 
| 272 | 
            +
                end
         | 
| 273 | 
            +
                
         | 
| 274 | 
            +
                def parse_tags(raw_tags)
         | 
| 275 | 
            +
                  return {} if raw_tags.nil?
         | 
| 276 | 
            +
                  
         | 
| 277 | 
            +
                  def to_symbol(string)
         | 
| 278 | 
            +
                    return string.gsub(/-/, "_").downcase.to_sym
         | 
| 279 | 
            +
                  end
         | 
| 280 | 
            +
                  
         | 
| 281 | 
            +
                  tags = {}
         | 
| 282 | 
            +
                  raw_tags.split(";").each do |tag|
         | 
| 283 | 
            +
                    tag_name, tag_value = tag.split("=")
         | 
| 284 | 
            +
                    if tag_value =~ /,/
         | 
| 285 | 
            +
                      tag_value = tag_value.split(',')
         | 
| 286 | 
            +
                    elsif tag_value.nil?
         | 
| 287 | 
            +
                      tag_value = tag_name
         | 
| 288 | 
            +
                    end
         | 
| 289 | 
            +
                    if tag_name =~ /\//
         | 
| 290 | 
            +
                      vendor, tag_name = tag_name.split('/')
         | 
| 291 | 
            +
                      tags[to_symbol(vendor)] = {
         | 
| 292 | 
            +
                        to_symbol(tag_name) => tag_value
         | 
| 293 | 
            +
                      }
         | 
| 294 | 
            +
                    else
         | 
| 295 | 
            +
                      tags[to_symbol(tag_name)] = tag_value
         | 
| 296 | 
            +
                    end
         | 
| 297 | 
            +
                  end
         | 
| 298 | 
            +
                  return tags
         | 
| 299 | 
            +
                end
         | 
| 300 | 
            +
             | 
| 301 | 
            +
                def parse_user
         | 
| 302 | 
            +
                  return unless @prefix
         | 
| 303 | 
            +
                  nick = @prefix[/^(\S+)!/, 1]
         | 
| 304 | 
            +
                  user = @prefix[/^\S+!(\S+)@/, 1]
         | 
| 305 | 
            +
                  host = @prefix[/@(\S+)$/, 1]
         | 
| 306 | 
            +
             | 
| 307 | 
            +
                  return nil if nick.nil?
         | 
| 308 | 
            +
                  return @bot.user_list.find_ensured(user, nick, host)
         | 
| 309 | 
            +
                end
         | 
| 310 | 
            +
             | 
| 311 | 
            +
                def parse_channel
         | 
| 312 | 
            +
                  # has to be called after parse_params
         | 
| 313 | 
            +
                  return nil if @params.empty?
         | 
| 314 | 
            +
             | 
| 315 | 
            +
                  case @command
         | 
| 316 | 
            +
                  when "INVITE", Constants::RPL_CHANNELMODEIS.to_s, Constants::RPL_BANLIST.to_s
         | 
| 317 | 
            +
                    @bot.channel_list.find_ensured(@params[1])
         | 
| 318 | 
            +
                  when Constants::RPL_NAMEREPLY.to_s
         | 
| 319 | 
            +
                    @bot.channel_list.find_ensured(@params[2])
         | 
| 320 | 
            +
                  else
         | 
| 321 | 
            +
                    # Note that this will also find channels for messages that
         | 
| 322 | 
            +
                    # don't actually include a channel parameter. For example
         | 
| 323 | 
            +
                    # `QUIT :#sometext` will be interpreted as a channel. The
         | 
| 324 | 
            +
                    # alternative to the currently used heuristic would be to
         | 
| 325 | 
            +
                    # hardcode a list of commands that provide a channel argument.
         | 
| 326 | 
            +
                    ch, status = privmsg_channel_name(@params.first)
         | 
| 327 | 
            +
                    if ch.nil? && numeric_reply? && @params.size > 1
         | 
| 328 | 
            +
                      ch, status = privmsg_channel_name(@params[1])
         | 
| 329 | 
            +
                    end
         | 
| 330 | 
            +
                    if ch
         | 
| 331 | 
            +
                      return @bot.channel_list.find_ensured(ch), status
         | 
| 332 | 
            +
                    end
         | 
| 333 | 
            +
                  end
         | 
| 334 | 
            +
                end
         | 
| 335 | 
            +
             | 
| 336 | 
            +
                def privmsg_channel_name(s)
         | 
| 337 | 
            +
                  chantypes = @bot.irc.isupport["CHANTYPES"]
         | 
| 338 | 
            +
                  statusmsg = @bot.irc.isupport["STATUSMSG"]
         | 
| 339 | 
            +
                  if statusmsg.include?(s[0]) && chantypes.include?(s[1])
         | 
| 340 | 
            +
                    status = @bot.irc.isupport["PREFIX"].invert[s[0]]
         | 
| 341 | 
            +
                    return s[1..-1], status
         | 
| 342 | 
            +
                  elsif chantypes.include?(s[0])
         | 
| 343 | 
            +
                    return s, nil
         | 
| 344 | 
            +
                  end
         | 
| 345 | 
            +
                end
         | 
| 346 | 
            +
             | 
| 347 | 
            +
                def parse_server
         | 
| 348 | 
            +
                  return unless @prefix
         | 
| 349 | 
            +
                  return if @prefix.match(/[@!]/)
         | 
| 350 | 
            +
                  return @prefix[/^(\S+)/, 1]
         | 
| 351 | 
            +
                end
         | 
| 352 | 
            +
             | 
| 353 | 
            +
                def parse_error
         | 
| 354 | 
            +
                  return @command.to_i if numeric_reply? && @command[/[45]\d\d/]
         | 
| 355 | 
            +
                end
         | 
| 356 | 
            +
             | 
| 357 | 
            +
                def parse_message
         | 
| 358 | 
            +
                  # has to be called after parse_params
         | 
| 359 | 
            +
                  if error?
         | 
| 360 | 
            +
                    @error.to_s
         | 
| 361 | 
            +
                  elsif regular_command?
         | 
| 362 | 
            +
                    @params.last
         | 
| 363 | 
            +
                  end
         | 
| 364 | 
            +
                end
         | 
| 365 | 
            +
             | 
| 366 | 
            +
                def parse_ctcp_message
         | 
| 367 | 
            +
                  # has to be called after parse_params
         | 
| 368 | 
            +
                  return unless ctcp?
         | 
| 369 | 
            +
                  @params.last =~ /\001(.+)\001/
         | 
| 370 | 
            +
                  $1
         | 
| 371 | 
            +
                end
         | 
| 372 | 
            +
             | 
| 373 | 
            +
                def parse_ctcp_command
         | 
| 374 | 
            +
                  # has to be called after parse_ctcp_message
         | 
| 375 | 
            +
                  return unless ctcp?
         | 
| 376 | 
            +
                  @ctcp_message.split(" ").first
         | 
| 377 | 
            +
                end
         | 
| 378 | 
            +
             | 
| 379 | 
            +
                def parse_ctcp_args
         | 
| 380 | 
            +
                  # has to be called after parse_ctcp_message
         | 
| 381 | 
            +
                  return unless ctcp?
         | 
| 382 | 
            +
                  @ctcp_message.split(" ")[1..-1]
         | 
| 383 | 
            +
                end
         | 
| 384 | 
            +
             | 
| 385 | 
            +
                def parse_action_message
         | 
| 386 | 
            +
                  # has to be called after parse_ctcp_message
         | 
| 387 | 
            +
                  return nil unless action?
         | 
| 388 | 
            +
                  @ctcp_message.split(" ", 2).last
         | 
| 389 | 
            +
                end
         | 
| 390 | 
            +
             | 
| 391 | 
            +
              end
         | 
| 392 | 
            +
            end
         |