cinch 2.0.12 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -24,33 +24,39 @@ Installation
24
24
 
25
25
  You can install the latest Cinch gem using RubyGems
26
26
 
27
- gem install cinch
27
+ ```
28
+ gem install cinch
29
+ ```
28
30
 
29
31
  ### GitHub
30
32
 
31
33
  Alternatively you can check out the latest code directly from Github
32
34
 
33
- git clone http://github.com/cinchrb/cinch.git
35
+ ```
36
+ git clone http://github.com/cinchrb/cinch.git
37
+ ```
34
38
 
35
39
  Example
36
40
  -------
37
41
 
38
42
  Your typical Hello, World application in Cinch would go something like this:
39
43
 
40
- require 'cinch'
44
+ ```ruby
45
+ require 'cinch'
41
46
 
42
- bot = Cinch::Bot.new do
43
- configure do |c|
44
- c.server = "irc.freenode.org"
45
- c.channels = ["#cinch-bots"]
46
- end
47
+ bot = Cinch::Bot.new do
48
+ configure do |c|
49
+ c.server = "irc.freenode.org"
50
+ c.channels = ["#cinch-bots"]
51
+ end
47
52
 
48
- on :message, "hello" do |m|
49
- m.reply "Hello, #{m.user.nick}"
50
- end
51
- end
53
+ on :message, "hello" do |m|
54
+ m.reply "Hello, #{m.user.nick}"
55
+ end
56
+ end
52
57
 
53
- bot.start
58
+ bot.start
59
+ ```
54
60
 
55
61
  More examples can be found in the `examples` directory.
56
62
 
@@ -98,27 +104,31 @@ your own plugins for others to use
98
104
 
99
105
  Want to see the same Hello, World application in plugin form? Sure you do!
100
106
 
101
- require 'cinch'
107
+ ```ruby
108
+ require 'cinch'
102
109
 
103
- class Hello
104
- include Cinch::Plugin
110
+ class Hello
111
+ include Cinch::Plugin
105
112
 
106
- match "hello"
113
+ match "hello"
107
114
 
108
- def execute(m)
109
- m.reply "Hello, #{m.user.nick}"
110
- end
111
- end
115
+ def execute(m)
116
+ m.reply "Hello, #{m.user.nick}"
117
+ end
118
+ end
112
119
 
113
- bot = Cinch::Bot.new do
114
- configure do |c|
115
- c.server = "irc.freenode.org"
116
- c.channels = ["#cinch-bots"]
117
- c.plugins.plugins = [Hello]
118
- end
119
- end
120
+ bot = Cinch::Bot.new do
121
+ configure do |c|
122
+ c.server = "irc.freenode.org"
123
+ c.channels = ["#cinch-bots"]
124
+ c.plugins.plugins = [Hello]
125
+ end
126
+ end
120
127
 
121
- bot.start
128
+ bot.start
129
+ ```
130
+
131
+ Note: Plugins take a default prefix of `/^!/` which means the actual match is `!hello`.
122
132
 
123
133
  More information can be found in the {Cinch::Plugin} documentation.
124
134
 
@@ -140,9 +150,11 @@ terminal, meaning you get some pretty damn awesome readable coloured
140
150
  text. Cinch also provides a way for your plugins to log custom
141
151
  messages:
142
152
 
143
- on :message, /hello/ do |m|
144
- debug "Someone said hello"
145
- end
153
+ ```ruby
154
+ on :message, /hello/ do |m|
155
+ debug "Someone said hello"
156
+ end
157
+ ```
146
158
 
147
159
  Authors
148
160
  -------
@@ -1,6 +1,67 @@
1
1
  # @title What has changed?
2
2
  # @markup kramdown
3
3
 
4
+ # What has changed in 2.1?
5
+ 1. Color stripping
6
+ 1. Per group hooks
7
+ 1. API improvements
8
+ 1. New methods
9
+ 1. Changed methods
10
+ 1. New aliases
11
+
12
+ ## Color stripping
13
+
14
+ The new method {Cinch::Utilities::String.strip_colors} allows removal
15
+ of mIRC color codes from messages.
16
+
17
+ Additionally, a new match option called `strip_colors` makes it
18
+ possible to automatically and temporarily strip color codes before
19
+ attemping to match a message.
20
+
21
+ ## Per group hooks
22
+
23
+ A new option `group` for hooks allows registering hooks for specific
24
+ groups.
25
+
26
+ ## API improvements
27
+
28
+ ### New methods
29
+
30
+ #### {Cinch::Bot}
31
+
32
+ - {Cinch::Bot#oper}
33
+
34
+ #### {Cinch::User}
35
+
36
+ - {Cinch::User#oper?}
37
+
38
+ #### {Cinch::Message}
39
+
40
+ - {Cinch::Message#action_reply}
41
+ - {Cinch::Message#safe_action_reply}
42
+
43
+ ### Changed methods
44
+
45
+ #### {Cinch::Handler}
46
+
47
+ - {Cinch::Handler#call} now returns the started thread.
48
+
49
+ #### {Cinch::HandlerList}
50
+
51
+ - {Cinch::HandlerList#dispatch} now returns the started threads.
52
+
53
+ ### New aliases
54
+
55
+ Due to some unfortunate naming mistakes in Cinch 2.0, Cinch 2.1 adds
56
+ several aliases. All of the new aliases deprecate the original method
57
+ names, which will be removed in Cinch 3.0.
58
+
59
+ #### {Cinch::User}
60
+ - {Cinch::User#monitored?} for {Cinch::User#monitored}
61
+ - {Cinch::User#synced?} for {Cinch::User#synced}
62
+
63
+
64
+
4
65
  # What has changed in 2.0?
5
66
  1. Added support for SASL
6
67
  1. Added support for DCC SEND
@@ -181,7 +242,6 @@ moved from {Cinch} to {Cinch::Constants}
181
242
  - {Cinch::Bot#modes}
182
243
  - {Cinch::Bot#modes=}
183
244
  - {Cinch::Bot#set_mode}
184
- - {Cinch::Bot#stop}
185
245
  - {Cinch::Bot#unset_mode}
186
246
  - {Cinch::Bot#user_list}
187
247
 
@@ -34,3 +34,27 @@ The following is not possible:
34
34
  # ...
35
35
  end
36
36
  end
37
+
38
+ # My handlers won't run, even though the Regexp matches.
39
+
40
+ Cinch plugins have a default prefix (`/^!/`). This allows for flexible prefixes
41
+ across the whole bot. The default prefix can be changed in `Bot#configure`:
42
+
43
+ x = Cinch::Bot.new do
44
+ configure do |c|
45
+ # ...
46
+ c.plugins.prefix = /^%/
47
+ end
48
+ end
49
+
50
+ Alternatively, you can set the prefix for specific plugins only, by calling `set`:
51
+
52
+ class MyPlygin
53
+ include Cinch::Plugin
54
+
55
+ set :prefix, /^%/
56
+ end
57
+
58
+ You can also choose to not use a prefix for specific matchers:
59
+
60
+ match /hi/, use_prefix: false
@@ -46,3 +46,12 @@ example:
46
46
  @bot.loggers.info "I miss my master :("
47
47
  end
48
48
  end
49
+
50
+ # Sending messages to users and channels beside `m.user` and `m.channel`
51
+
52
+ Cinch provides {Cinch::Helpers helper methods} to get instances of Channel
53
+ and User objects that you can work with:
54
+
55
+ User('user').send("Hello!") # Sends a message to user 'user'
56
+ Channel('#channel').send("Hello!") # Sends a message to channel '#channel'
57
+
@@ -41,7 +41,7 @@ The level can be changed for single loggers or all loggers at once, by either us
41
41
  Example:
42
42
 
43
43
  bot = Cinch::Bot.new { }
44
- bot.loggers << Cinch::Loggers::FormattedLogger.new(File.open("/tmp/log.log", "a"))
44
+ bot.loggers << Cinch::Logger::FormattedLogger.new(File.open("/tmp/log.log", "a"))
45
45
  bot.loggers.level = :debug
46
46
  bot.loggers.first.level = :info
47
47
 
@@ -8,7 +8,7 @@ class RandomNumberGenerator
8
8
  def start
9
9
  while true
10
10
  sleep 5 # pretend that we are waiting for some kind of entropy
11
- @bot.dispatch(:random_number, nil, Kernel.rand)
11
+ @bot.handlers.dispatch(:random_number, nil, Kernel.rand)
12
12
  end
13
13
  end
14
14
  end
@@ -177,10 +177,10 @@ module Cinch
177
177
  # right event will be checked against this argument and the event
178
178
  # will only be called if it matches
179
179
  #
180
- # @param [Array<Object>] *args Arguments that should be passed to
180
+ # @param [Array<Object>] args Arguments that should be passed to
181
181
  # the block, additionally to capture groups of the regexp.
182
182
  #
183
- # @yieldparam [String] *args each capture group of the regex will
183
+ # @yieldparam [Array<String>] args each capture group of the regex will
184
184
  # be one argument to the block.
185
185
  #
186
186
  # @return [Handler] The handlers that have been registered
@@ -266,6 +266,8 @@ module Cinch
266
266
  }
267
267
  end
268
268
 
269
+ @modes = []
270
+
269
271
  @loggers.info "Connecting to #{@config.server}:#{@config.port}"
270
272
  @irc = IRC.new(self)
271
273
  @irc.start
@@ -424,6 +426,18 @@ module Cinch
424
426
  @irc.send "NICK #{new_nick}"
425
427
  end
426
428
 
429
+ # Gain oper privileges.
430
+ #
431
+ # @param [String] password
432
+ # @param [String] user The username to use. Defaults to the bot's
433
+ # nickname
434
+ # @since 2.1.0
435
+ # @return [void]
436
+ def oper(password, user = nil)
437
+ user ||= self.nick
438
+ @irc.send "OPER #{user} #{password}"
439
+ end
440
+
427
441
  # Try to create a free nick, first by cycling through all
428
442
  # available alternatives and then by appending underscores.
429
443
  #
@@ -13,8 +13,8 @@ module Cinch
13
13
  end
14
14
 
15
15
  # (see Bot#synchronize)
16
- def synchronize(*args, &block)
17
- @bot.synchronize(*args, &block)
16
+ def synchronize(name, &block)
17
+ @bot.synchronize(name, &block)
18
18
  end
19
19
  end
20
20
  end
@@ -59,7 +59,7 @@ module Cinch
59
59
 
60
60
  @synced_attributes = Set.new
61
61
  @when_requesting_synced_attribute = lambda {|attr|
62
- if @in_channel && attr == :topic && !synced?(:topic)
62
+ if @in_channel && attr == :topic && !attribute_synced?(:topic)
63
63
  # Even if we are in the channel, if there's no topic set,
64
64
  # the attribute won't be synchronised yet. Explicitly
65
65
  # request the topic.
@@ -321,7 +321,7 @@ module Cinch
321
321
  # Kicks a user from the channel.
322
322
  #
323
323
  # @param [String, User] user the user to kick
324
- # @param [String] a reason for the kick
324
+ # @param [String] reason a reason for the kick
325
325
  # @raise [Exceptions::KickReasonTooLong]
326
326
  # @return [void]
327
327
  def kick(user, reason = nil)
@@ -70,7 +70,7 @@ module Cinch
70
70
  :reset => 15.chr,
71
71
  }
72
72
 
73
- # @param [Array<Symbol>] *settings The colors and attributes to apply.
73
+ # @param [Array<Symbol>] settings The colors and attributes to apply.
74
74
  # When supplying two colors, the first will be used for the
75
75
  # foreground and the second for the background.
76
76
  # @param [String] string The string to format.
@@ -19,6 +19,9 @@ module Cinch
19
19
  # @return [Symbol]
20
20
  attr_reader :group
21
21
 
22
+ # @return [Boolean]
23
+ attr_reader :strip_colors
24
+
22
25
  # @return [ThreadGroup]
23
26
  # @api private
24
27
  attr_reader :thread_group
@@ -31,15 +34,23 @@ module Cinch
31
34
  # to.
32
35
  # @option options [Boolean] :execute_in_callback (false) Whether
33
36
  # to execute the handler in an instance of {Callback}
37
+ # @option options [Boolean] :strip_colors (false) Strip colors
38
+ # from message before attemping match
34
39
  # @option options [Array] :args ([]) Additional arguments to pass
35
40
  # to the block
36
41
  def initialize(bot, event, pattern, options = {}, &block)
37
- options = {:group => nil, :execute_in_callback => false, :args => []}.merge(options)
42
+ options = {
43
+ :group => nil,
44
+ :execute_in_callback => false,
45
+ :strip_colors => false,
46
+ :args => []
47
+ }.merge(options)
38
48
  @bot = bot
39
49
  @event = event
40
50
  @pattern = pattern
41
51
  @group = options[:group]
42
52
  @execute_in_callback = options[:execute_in_callback]
53
+ @strip_colors = options[:strip_colors]
43
54
  @args = options[:args]
44
55
  @block = block
45
56
 
@@ -74,11 +85,11 @@ module Cinch
74
85
  # @param [Message] message Message that caused the invocation
75
86
  # @param [Array] captures Capture groups of the pattern that are
76
87
  # being passed as arguments
77
- # @return [void]
88
+ # @return [Thread]
78
89
  def call(message, captures, arguments)
79
90
  bargs = captures + arguments
80
91
 
81
- @thread_group.add Thread.new {
92
+ thread = Thread.new {
82
93
  @bot.loggers.debug "[New thread] For #{self}: #{Thread.current} -- #{@thread_group.list.size} in total."
83
94
 
84
95
  begin
@@ -93,6 +104,9 @@ module Cinch
93
104
  @bot.loggers.debug "[Thread done] For #{self}: #{Thread.current} -- #{@thread_group.list.size - 1} remaining."
94
105
  end
95
106
  }
107
+
108
+ @thread_group.add(thread)
109
+ thread
96
110
  end
97
111
 
98
112
  # @return [String]
@@ -19,7 +19,7 @@ module Cinch
19
19
  end
20
20
  end
21
21
 
22
- # @param [Handler, Array<Handler>] *handlers The handlers to unregister
22
+ # @param [Handler, Array<Handler>] handlers The handlers to unregister
23
23
  # @return [void]
24
24
  # @see Handler#unregister
25
25
  def unregister(*handlers)
@@ -39,7 +39,7 @@ module Cinch
39
39
  end
40
40
 
41
41
  handlers = handlers.select { |handler|
42
- msg.match(handler.pattern.to_r(msg), type)
42
+ msg.match(handler.pattern.to_r(msg), type, handler.strip_colors)
43
43
  }.group_by {|handler| handler.group}
44
44
 
45
45
  handlers.values_at(*(handlers.keys - [nil])).map(&:first) + (handlers[nil] || [])
@@ -49,10 +49,12 @@ module Cinch
49
49
  # @param [Symbol] event The event type
50
50
  # @param [Message, nil] msg The message which is responsible for
51
51
  # and attached to the event, or nil.
52
- # @param [Array] *arguments A list of additional arguments to pass
52
+ # @param [Array] arguments A list of additional arguments to pass
53
53
  # to event handlers
54
- # @return [void]
54
+ # @return [Array<Thread>]
55
55
  def dispatch(event, msg = nil, *arguments)
56
+ threads = []
57
+
56
58
  if handlers = find(event, msg)
57
59
  already_run = Set.new
58
60
  handlers.each do |handler|
@@ -61,14 +63,16 @@ module Cinch
61
63
  # calling Message#match multiple times is not a problem
62
64
  # because we cache the result
63
65
  if msg
64
- captures = msg.match(handler.pattern.to_r(msg), event).captures
66
+ captures = msg.match(handler.pattern.to_r(msg), event, handler.strip_colors).captures
65
67
  else
66
68
  captures = []
67
69
  end
68
70
 
69
- handler.call(msg, captures, arguments)
71
+ threads << handler.call(msg, captures, arguments)
70
72
  end
71
73
  end
74
+
75
+ threads
72
76
  end
73
77
 
74
78
  # @yield [handler] Yields all registered handlers
@@ -179,8 +179,8 @@ module Cinch
179
179
  # @group Formatting
180
180
 
181
181
  # (see Formatting.format)
182
- def Format(*args)
183
- Formatting.format(*args)
182
+ def Format(*settings, string)
183
+ Formatting.format(*settings, string)
184
184
  end
185
185
  alias_method :Color, :Format
186
186
 
@@ -493,7 +493,7 @@ module Cinch
493
493
  modes = ModeParser.parse_modes(msg.params[1], msg.params[2..-1])
494
494
  modes.each do |direction, mode, _|
495
495
  if direction == :add
496
- @bot.modes << mode
496
+ @bot.modes << mode unless @bot.modes.include?(mode)
497
497
  else
498
498
  @bot.modes.delete(mode)
499
499
  end
@@ -629,6 +629,12 @@ module Cinch
629
629
  })
630
630
  end
631
631
 
632
+ def on_313(msg, events)
633
+ # RPL_WHOISOPERATOR
634
+ user = User(msg.params[1])
635
+ @whois_updates[user].merge!({:oper? => true})
636
+ end
637
+
632
638
  def on_317(msg, events)
633
639
  # RPL_WHOISIDLE
634
640
  user = User(msg.params[1])
@@ -55,8 +55,8 @@ module Cinch
55
55
  end
56
56
 
57
57
  # (see Logger#exception)
58
- def exception(message)
59
- each {|l| l.exception(message)}
58
+ def exception(e)
59
+ each {|l| l.exception(e)}
60
60
  end
61
61
  end
62
62
  end
@@ -50,7 +50,7 @@ module Cinch
50
50
  @mask.dup
51
51
  end
52
52
 
53
- # @param [String, #mask]
53
+ # @param [String, #mask] target
54
54
  # @return [target] if already a Mask
55
55
  # @return [Mask]
56
56
  # @version 2.0.0
@@ -1,5 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  require "time"
3
+ require "cinch/utilities/string"
3
4
 
4
5
  module Cinch
5
6
  # This class serves two purposes. For one, it simply
@@ -135,14 +136,23 @@ module Cinch
135
136
 
136
137
  # @api private
137
138
  # @return [MatchData]
138
- def match(regexp, type)
139
- if type == :ctcp
140
- @matches[:ctcp][regexp] ||= ctcp_message.match(regexp)
141
- elsif type == :action
142
- @matches[:action][regexp] ||= action_message.match(regexp)
139
+ def match(regexp, type, strip_colors)
140
+ text = ""
141
+ case type
142
+ when :ctcp
143
+ text = ctcp_message
144
+ when :action
145
+ text = action_message
143
146
  else
144
- @matches[:other][regexp] ||= message.to_s.match(regexp)
147
+ text = message.to_s
148
+ type = :other
145
149
  end
150
+
151
+ if strip_colors
152
+ text = Cinch::Utilities::String.strip_colors(text)
153
+ end
154
+
155
+ @matches[type][regexp] ||= text.match(regexp)
146
156
  end
147
157
 
148
158
  # @group Replying
@@ -176,6 +186,24 @@ module Cinch
176
186
  @target.safe_send(text)
177
187
  end
178
188
 
189
+ # Reply to a message with an action.
190
+ #
191
+ # @param [String] text the action message
192
+ # @return [void]
193
+ def action_reply(text)
194
+ text = text.to_s
195
+ @target.action(text)
196
+ end
197
+
198
+ # Like #action_reply, but using {Target#safe_action} instead
199
+ #
200
+ # @param (see #action_reply)
201
+ # @return (see #action_reply)
202
+ def safe_action_reply(text)
203
+ text = text.to_s
204
+ @target.safe_action(text)
205
+ end
206
+
179
207
  # Reply to a CTCP message
180
208
  #
181
209
  # @return [void]
@@ -1,3 +1,5 @@
1
+ require "cinch/helpers"
2
+
1
3
  # TODO more details in "message dropped" debug output
2
4
  module Cinch
3
5
  # This class represents the core of the plugin functionality of
@@ -79,7 +81,15 @@ module Cinch
79
81
  # @attr [Boolean] use_suffix
80
82
  # @attr [Symbol] method
81
83
  # @attr [Symbol] group
82
- Matcher = Struct.new(:pattern, :use_prefix, :use_suffix, :method, :group, :prefix, :suffix, :react_on)
84
+ Matcher = Struct.new(:pattern,
85
+ :use_prefix,
86
+ :use_suffix,
87
+ :method,
88
+ :group,
89
+ :prefix,
90
+ :suffix,
91
+ :react_on,
92
+ :strip_colors)
83
93
 
84
94
  # Represents a Listener as created by {#listen_to}.
85
95
  #
@@ -105,7 +115,7 @@ module Cinch
105
115
  # @attr [Symbol] type
106
116
  # @attr [Array<Symbol>] for
107
117
  # @attr [Symbol] method
108
- Hook = Struct.new(:type, :for, :method)
118
+ Hook = Struct.new(:type, :for, :method, :group)
109
119
 
110
120
  # @api private
111
121
  def self.extended(by)
@@ -179,14 +189,32 @@ module Cinch
179
189
  # @option options [Symbol, Fixnum] react_on (:message) The
180
190
  # {file:docs/events.md event} to react on.
181
191
  # @option options [Symbol] :group (nil) The group the match belongs to.
192
+ # @option options [Boolean] :strip_colors (false) Strip colors
193
+ # from message before attempting match
182
194
  # @return [Matcher]
183
195
  # @todo Document match/listener grouping
184
196
  def match(pattern, options = {})
185
- options = {:use_prefix => true, :use_suffix => true, :method => :execute, :group => nil, :prefix => nil, :suffix => nil, :react_on => nil}.merge(options)
197
+ options = {
198
+ :use_prefix => true,
199
+ :use_suffix => true,
200
+ :method => :execute,
201
+ :group => nil,
202
+ :prefix => nil,
203
+ :suffix => nil,
204
+ :react_on => nil,
205
+ :strip_colors => false,
206
+ }.merge(options)
186
207
  if options[:react_on]
187
208
  options[:react_on] = options[:react_on].to_s.to_sym
188
209
  end
189
- matcher = Matcher.new(pattern, *options.values_at(:use_prefix, :use_suffix, :method, :group, :prefix, :suffix, :react_on))
210
+ matcher = Matcher.new(pattern, *options.values_at(:use_prefix,
211
+ :use_suffix,
212
+ :method,
213
+ :group,
214
+ :prefix,
215
+ :suffix,
216
+ :react_on,
217
+ :strip_colors))
190
218
  @matchers << matcher
191
219
 
192
220
  matcher
@@ -253,12 +281,15 @@ module Cinch
253
281
  # a handler?
254
282
  # @option options [Array<:match, :listen_to, :ctcp>] :for ([:match, :listen_to, :ctcp])
255
283
  # Which kinds of events to run the hook for.
256
- # @option options [Symbol] :method (true) The method to execute.
284
+ # @option options [Symbol] :method (:hook) The method to execute.
285
+ # @option options [Symbol] :group (nil) The match group to
286
+ # execute the hook for. Hooks belonging to the `nil` group
287
+ # will execute for all matches.
257
288
  # @return [Hook]
258
289
  # @since 1.1.0
259
290
  def hook(type, options = {})
260
- options = {:for => [:match, :listen_to, :ctcp], :method => :hook}.merge(options)
261
- hook = Hook.new(type, options[:for], options[:method])
291
+ options = {:for => [:match, :listen_to, :ctcp], :method => :hook, :group => nil}.merge(options)
292
+ hook = Hook.new(type, options[:for], options[:method], options[:group])
262
293
  __hooks(type) << hook
263
294
 
264
295
  hook
@@ -266,7 +297,7 @@ module Cinch
266
297
 
267
298
  # @return [Hash]
268
299
  # @api private
269
- def __hooks(type = nil, events = nil)
300
+ def __hooks(type = nil, events = nil, group = nil)
270
301
  if type.nil?
271
302
  hooks = @hooks
272
303
  else
@@ -280,14 +311,16 @@ module Cinch
280
311
  if hooks.is_a?(Hash)
281
312
  hooks = hooks.map { |k, v| v }
282
313
  end
283
- return hooks.select { |hook| (events & hook.for).size > 0 }
314
+ hooks.select! { |hook| (events & hook.for).size > 0 }
284
315
  end
316
+
317
+ return hooks.select { |hook| hook.group.nil? || hook.group == group }
285
318
  end
286
319
 
287
320
  # @return [Boolean] True if processing should continue
288
321
  # @api private
289
- def call_hooks(type, event, instance, args)
290
- stop = __hooks(type, event).find { |hook|
322
+ def call_hooks(type, event, group, instance, args)
323
+ stop = __hooks(type, event, group).find { |hook|
291
324
  # stop after the first hook that returns false
292
325
  if hook.method.respond_to?(:call)
293
326
  instance.instance_exec(*args, &hook.method) == false
@@ -314,9 +347,9 @@ module Cinch
314
347
  self.class.listeners.each do |listener|
315
348
  @bot.loggers.debug "[plugin] #{self.class.plugin_name}: Registering listener for type `#{listener.event}`"
316
349
  new_handler = Handler.new(@bot, listener.event, Pattern.new(nil, //, nil)) do |message, *args|
317
- if self.class.call_hooks(:pre, :listen_to, self, [message])
350
+ if self.class.call_hooks(:pre, :listen_to, nil, self, [message])
318
351
  __send__(listener.method, message, *args)
319
- self.class.call_hooks(:post, :listen_to, self, [message])
352
+ self.class.call_hooks(:post, :listen_to, nil, self, [message])
320
353
  else
321
354
  @bot.loggers.debug "[plugin] #{self.class.plugin_name}: Dropping message due to hook"
322
355
  end
@@ -332,9 +365,9 @@ module Cinch
332
365
  self.class.ctcps.each do |ctcp|
333
366
  @bot.loggers.debug "[plugin] #{self.class.plugin_name}: Registering CTCP `#{ctcp}`"
334
367
  new_handler = Handler.new(@bot, :ctcp, Pattern.generate(:ctcp, ctcp)) do |message, *args|
335
- if self.class.call_hooks(:pre, :ctcp, self, [message])
368
+ if self.class.call_hooks(:pre, :ctcp, nil, self, [message])
336
369
  __send__("ctcp_#{ctcp.downcase}", message, *args)
337
- self.class.call_hooks(:post, :ctcp, self, [message])
370
+ self.class.call_hooks(:post, :ctcp, nil, self, [message])
338
371
  else
339
372
  @bot.loggers.debug "[plugin] #{self.class.plugin_name}: Dropping message due to hook"
340
373
  end
@@ -370,7 +403,11 @@ module Cinch
370
403
 
371
404
  @bot.loggers.debug "[plugin] #{self.class.plugin_name}: Registering executor with pattern `#{pattern_to_register.inspect}`, reacting on `#{react_on}`"
372
405
 
373
- new_handler = Handler.new(@bot, react_on, pattern_to_register, group: matcher.group) do |message, *args|
406
+ new_handler = Handler.new(@bot,
407
+ react_on,
408
+ pattern_to_register,
409
+ group: matcher.group,
410
+ strip_colors: matcher.strip_colors) do |message, *args|
374
411
  method = method(matcher.method)
375
412
  arity = method.arity - 1
376
413
  if arity > 0
@@ -378,9 +415,9 @@ module Cinch
378
415
  elsif arity == 0
379
416
  args = []
380
417
  end
381
- if self.class.call_hooks(:pre, :match, self, [message])
418
+ if self.class.call_hooks(:pre, :match, matcher.group, self, [message])
382
419
  method.call(message, *args)
383
- self.class.call_hooks(:post, :match, self, [message])
420
+ self.class.call_hooks(:post, :match, matcher.group, self, [message])
384
421
  else
385
422
  @bot.loggers.debug "[plugin] #{self.class.plugin_name}: Dropping message due to hook"
386
423
  end
@@ -454,8 +491,8 @@ module Cinch
454
491
  end
455
492
 
456
493
  # (see Bot#synchronize)
457
- def synchronize(*args, &block)
458
- @bot.synchronize(*args, &block)
494
+ def synchronize(name, &block)
495
+ @bot.synchronize(name, &block)
459
496
  end
460
497
 
461
498
  # Provides access to plugin-specific options.
@@ -55,18 +55,12 @@ module Cinch
55
55
  password << "." * (8 - (password.size % 8))
56
56
 
57
57
  crypted = ""
58
- cipher = OpenSSL::Cipher.new("BF")
58
+ cipher = OpenSSL::Cipher.new("BF-ECB")
59
+ cipher.key_len = 32 # OpenSSL's default of 16 doesn't work
60
+ cipher.encrypt
61
+ cipher.key = secret
59
62
 
60
- while password.size > 0 do
61
- # We have to reinitialize this every time because for OpenSSL, "BF" is synonynmous with "BF-CBC", and we do not want CBC
62
- cipher.reset
63
- cipher.key_len = 32 # OpenSSL's default of 16 doesn't work
64
- cipher.encrypt
65
- cipher.key = secret
66
-
67
- clear = password.slice!(0, 8)
68
- crypted << cipher.update(clear) # we do not want the content of cipher.final
69
- end
63
+ crypted = cipher.update(password) # we do not want the content of cipher.final
70
64
 
71
65
  answer = [public.bytesize, public, user, crypted].pack("na*Z*a*")
72
66
  Base64.strict_encode64(answer)
@@ -9,7 +9,7 @@ module Cinch
9
9
  attr = attr.to_sym
10
10
  waited = 0
11
11
  while true
12
- return if synced?(attr)
12
+ return if attribute_synced?(attr)
13
13
  waited += 1
14
14
 
15
15
  if waited % 100 == 0
@@ -38,7 +38,7 @@ module Cinch
38
38
 
39
39
  # @return [Boolean]
40
40
  # @api private
41
- def synced?(attribute)
41
+ def attribute_synced?(attribute)
42
42
  @synced_attributes.include?(attribute)
43
43
  end
44
44
 
@@ -134,7 +134,7 @@ module Cinch
134
134
  self == other
135
135
  end
136
136
 
137
- # @param [Target, String]
137
+ # @param [Target, String] other
138
138
  # @return [-1, 0, 1, nil]
139
139
  def <=>(other)
140
140
  casemapping = @bot.irc.isupport["CASEMAPPING"]
@@ -3,22 +3,32 @@ require "cinch/target"
3
3
  require "timeout"
4
4
 
5
5
  module Cinch
6
- # @attr_reader [String] user
7
- # @attr_reader [String] host
8
- # @attr_reader [String] realname
9
6
  # @attr_reader [String] authname
7
+ # @attr_reader [String, nil] away The user's away message, or
8
+ # `nil` if not away.
9
+ # @attr_reader [Array<Channel>] channels All channels the user is
10
+ # in.
11
+ # @attr_reader [String] host
10
12
  # @attr_reader [Integer] idle How long this user has been idle, in seconds.
11
13
  # This is a snapshot of the last WHOIS.
14
+ # @attr_reader [String] nick The user's nickname
15
+ # @attr_reader [Boolean] online True if the user is online.
16
+ # @attr_reader [Boolean] oper True if the user is an IRC operator.
17
+ # @attr_reader [String] realname
18
+ # @attr_reader [Boolean] secure True if the user is using a secure
19
+ # connection, i.e. SSL.
12
20
  # @attr_reader [Time] signed_on_at
13
- # @attr_reader [Array<Channel>] channels All channels the user is in.
14
- # @attr_reader [String, nil] away The user's away message, or
15
- # `nil` if not away.
21
+ # @attr_reader [Boolean] unknown True if the instance references an user who
22
+ # cannot be found on the server.
23
+ # @attr_reader [String] user
16
24
  #
17
25
  # @version 2.0.0
18
26
  class User < Target
19
27
  include Syncable
20
28
 
21
- alias_method :nick, :name
29
+ def nick
30
+ name
31
+ end
22
32
 
23
33
  # @return [String]
24
34
  # @since 1.1.0
@@ -26,45 +36,126 @@ module Cinch
26
36
 
27
37
  # @return [Boolean]
28
38
  attr_reader :synced
39
+ # @since 2.1.0
40
+ alias_method :synced?, :synced
29
41
 
30
42
  # @return [Boolean]
31
- attr_reader :in_whois
32
-
33
43
  # @api private
34
- attr_writer :in_whois
44
+ attr_accessor :in_whois
45
+
46
+ def user
47
+ attr(:user, true, false)
48
+ end
49
+
50
+ def host
51
+ attr(:host, true, false)
52
+ end
53
+
54
+ def realname
55
+ attr(:realname, true, false)
56
+ end
57
+
58
+ def authname
59
+ attr(:authname, true, false)
60
+ end
61
+
62
+ def idle
63
+ attr(:idle, true, false)
64
+ end
65
+
66
+ def signed_on_at
67
+ attr(:signed_on_at, true, false)
68
+ end
35
69
 
36
- # @return [Boolean] True if the instance references an user who
37
- # cannot be found on the server.
38
- attr_reader :unknown
39
- alias_method :unknown?, :unknown
40
- undef_method "unknown?"
41
- undef_method "unknown"
42
70
  def unknown
43
- self.unknown?
71
+ attr(:unknown?, true, false)
44
72
  end
73
+ alias_method :unknown?, :unknown
45
74
 
46
- # @return [Boolean] True if the user is online.
47
75
  # @note This attribute will be updated by various events, but
48
76
  # unless {#monitor} is being used, this information cannot be
49
77
  # ensured to be always correct.
50
- attr_reader :online
51
- alias_method :online?, :online
52
- undef_method "online?"
53
- undef_method "online"
54
78
  def online
55
- self.online?
79
+ attr(:online?, true, false)
80
+ end
81
+ alias_method :online?, :online
82
+
83
+ def channels
84
+ attr(:channels, true, false)
56
85
  end
57
86
 
58
- # @return [Boolean] True if the user is using a secure connection, i.e. SSL.
59
- attr_reader :secure
60
- alias_method :secure?, :secure
61
- undef_method "secure?"
62
- undef_method "secure"
63
87
  def secure
64
- self.secure?
88
+ attr(:secure?, true, false)
89
+ end
90
+ alias_method :secure?, :secure
91
+
92
+ # @since 2.1.0
93
+ def oper
94
+ attr(:oper?, true, false)
95
+ end
96
+ alias_method :oper?, :oper
97
+
98
+ # @private
99
+ def user_unsynced
100
+ attr(:user, true, true)
101
+ end
102
+
103
+ # @private
104
+ def host_unsynced
105
+ attr(:host, true, true)
106
+ end
107
+
108
+ # @private
109
+ def realname_unsynced
110
+ attr(:realname, true, true)
111
+ end
112
+
113
+ # @private
114
+ def authname_unynced
115
+ attr(:authname, true, true)
116
+ end
117
+
118
+ # @private
119
+ def idle_unsynced
120
+ attr(:idle, true, true)
121
+ end
122
+
123
+ # @private
124
+ def signed_on_at_unsynced
125
+ attr(:signed_on_at, true, true)
126
+ end
127
+
128
+ # @private
129
+ def unknown_unsynced
130
+ attr(:unknown?, true, true)
131
+ end
132
+ alias_method "unknown?_unsynced", "unknown_unsynced"
133
+
134
+ # @private
135
+ def online_unsynced
136
+ attr(:online?, true, true)
65
137
  end
138
+ alias_method "online?_unsynced", "online_unsynced"
66
139
 
67
- # By default, you can use methods like User#user, User#host and
140
+ # @private
141
+ def channels_unsynced
142
+ attr(:channels, true, true)
143
+ end
144
+
145
+ # @private
146
+ def secure_unsynced
147
+ attr(:secure?, true, true)
148
+ end
149
+ alias_method "secure?_unsynced", "secure_unsynced"
150
+
151
+ # @private
152
+ # @since 2.1.0
153
+ def oper_unsynced
154
+ attr(:oper?, true, true)
155
+ end
156
+ alias_method "oper?_unsynced", "oper_unsynced"
157
+
158
+ # By default, you can use methods like {#user}, {#host} and
68
159
  # alike – If you however fear that another thread might change
69
160
  # data while you're using it and if this means a critical issue to
70
161
  # your code, you can store a clone of the result of this method
@@ -82,7 +173,10 @@ module Cinch
82
173
  # @return [Boolean] True if the user is being monitored
83
174
  # @see #monitor
84
175
  # @see #unmonitor
176
+ # @note The attribute writer is in fact part of the private API
85
177
  attr_reader :monitored
178
+ # @since 2.1.0
179
+ alias_method :monitored?, :monitored
86
180
 
87
181
  # @api private
88
182
  attr_writer :monitored
@@ -101,6 +195,7 @@ module Cinch
101
195
  :channels => [],
102
196
  :secure? => false,
103
197
  :away => nil,
198
+ :oper? => false,
104
199
  }
105
200
  case args.size
106
201
  when 2
@@ -114,7 +209,7 @@ module Cinch
114
209
  @synced_attributes = Set.new
115
210
 
116
211
  @when_requesting_synced_attribute = lambda {|attr|
117
- unless synced?(attr)
212
+ unless attribute_synced?(attr)
118
213
  @data[:unknown?] = false
119
214
  unsync :unknown?
120
215
 
@@ -197,8 +292,9 @@ module Cinch
197
292
 
198
293
  {
199
294
  :authname => nil,
200
- :idle => 0,
201
- :secure? => false,
295
+ :idle => 0,
296
+ :secure? => false,
297
+ :oper? => false,
202
298
  :channels => [],
203
299
  }.merge(values).each do |attr, value|
204
300
  sync(attr, value, true)
@@ -371,28 +467,5 @@ module Cinch
371
467
  unsync(:authname)
372
468
  @bot.user_list.update_nick(self)
373
469
  end
374
-
375
- # Provides synced access to user attributes.
376
- def method_missing(m, *args)
377
- if m.to_s =~ /^(.+)_unsynced$/
378
- m = $1.to_sym
379
- unsync = true
380
- end
381
-
382
- if @data.has_key?(m)
383
- attr(m, true, unsync)
384
- else
385
- super
386
- end
387
- end
388
-
389
- # @since 1.1.2
390
- def respond_to?(m, include_all = false)
391
- if m.to_s =~ /^(.+)_unsynced$/
392
- m = $1.to_sym
393
- end
394
-
395
- return @data.has_key?(m) || super
396
- end
397
470
  end
398
471
  end
@@ -8,6 +8,10 @@ module Cinch
8
8
  def self.filter_string(string)
9
9
  string.gsub(/[\x00-\x1f]/, '')
10
10
  end
11
+
12
+ def self.strip_colors(string)
13
+ string.gsub(/[\x02\x0f\x16\x1f\x12]|\x03(\d{1,2}(,\d{1,2})?)?/, '')
14
+ end
11
15
  end
12
16
  end
13
17
  end
@@ -1,4 +1,4 @@
1
1
  module Cinch
2
2
  # Version of the library
3
- VERSION = '2.0.12'
3
+ VERSION = '2.1.0'
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cinch
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.12
4
+ version: 2.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-01-30 00:00:00.000000000 Z
12
+ date: 2014-02-27 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: A simple, friendly DSL for creating IRC bots
15
15
  email:
@@ -103,7 +103,6 @@ files:
103
103
  - examples/plugins/own_events.rb
104
104
  - examples/plugins/seen.rb
105
105
  - examples/plugins/last_nick.rb
106
- - examples/plugins/urban_dict.rb
107
106
  - examples/plugins/hooks.rb
108
107
  - examples/plugins/hello.rb
109
108
  - examples/plugins/custom_prefix.rb
@@ -142,3 +141,4 @@ signing_key:
142
141
  specification_version: 3
143
142
  summary: An IRC Bot Building Framework
144
143
  test_files: []
144
+ has_rdoc: yard
@@ -1,30 +0,0 @@
1
- require 'cinch'
2
- require 'open-uri'
3
- require 'nokogiri'
4
- require 'cgi'
5
-
6
- class UrbanDictionary
7
- include Cinch::Plugin
8
-
9
- match /urban (.+)/
10
- def lookup(word)
11
- url = "http://www.urbandictionary.com/define.php?term=#{CGI.escape(word)}"
12
- CGI.unescape_html Nokogiri::HTML(open(url)).at("div.definition").text.gsub(/\s+/, ' ') rescue nil
13
- end
14
-
15
- def execute(m, word)
16
- m.reply(lookup(word) || "No results found", true)
17
- end
18
- end
19
-
20
- bot = Cinch::Bot.new do
21
- configure do |c|
22
- c.server = "irc.freenode.net"
23
- c.nick = "MrCinch"
24
- c.channels = ["#cinch-bots"]
25
- c.plugins.plugins = [UrbanDictionary]
26
- end
27
- end
28
-
29
- bot.start
30
-