cinch 1.1.3 → 2.0.0.pre.1

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.
Files changed (83) hide show
  1. data/LICENSE +1 -0
  2. data/README.md +3 -3
  3. data/docs/bot_options.md +435 -0
  4. data/docs/changes.md +440 -0
  5. data/docs/common_mistakes.md +35 -0
  6. data/docs/common_tasks.md +47 -0
  7. data/docs/encodings.md +67 -0
  8. data/docs/events.md +272 -0
  9. data/docs/logging.md +5 -0
  10. data/docs/migrating.md +267 -0
  11. data/docs/readme.md +18 -0
  12. data/examples/plugins/custom_prefix.rb +1 -1
  13. data/examples/plugins/dice_roll.rb +38 -0
  14. data/examples/plugins/lambdas.rb +1 -1
  15. data/examples/plugins/memo.rb +16 -10
  16. data/examples/plugins/url_shorten.rb +1 -0
  17. data/lib/cinch.rb +5 -60
  18. data/lib/cinch/ban.rb +13 -7
  19. data/lib/cinch/bot.rb +228 -403
  20. data/lib/cinch/{cache_manager.rb → cached_list.rb} +5 -1
  21. data/lib/cinch/callback.rb +3 -0
  22. data/lib/cinch/channel.rb +119 -195
  23. data/lib/cinch/{channel_manager.rb → channel_list.rb} +6 -3
  24. data/lib/cinch/configuration.rb +73 -0
  25. data/lib/cinch/configuration/bot.rb +47 -0
  26. data/lib/cinch/configuration/dcc.rb +16 -0
  27. data/lib/cinch/configuration/plugins.rb +41 -0
  28. data/lib/cinch/configuration/sasl.rb +17 -0
  29. data/lib/cinch/configuration/ssl.rb +19 -0
  30. data/lib/cinch/configuration/storage.rb +37 -0
  31. data/lib/cinch/configuration/timeouts.rb +14 -0
  32. data/lib/cinch/constants.rb +531 -369
  33. data/lib/cinch/dcc.rb +12 -0
  34. data/lib/cinch/dcc/dccable_object.rb +37 -0
  35. data/lib/cinch/dcc/incoming.rb +1 -0
  36. data/lib/cinch/dcc/incoming/send.rb +131 -0
  37. data/lib/cinch/dcc/outgoing.rb +1 -0
  38. data/lib/cinch/dcc/outgoing/send.rb +115 -0
  39. data/lib/cinch/exceptions.rb +8 -1
  40. data/lib/cinch/formatting.rb +106 -0
  41. data/lib/cinch/handler.rb +104 -0
  42. data/lib/cinch/handler_list.rb +86 -0
  43. data/lib/cinch/helpers.rb +167 -10
  44. data/lib/cinch/irc.rb +525 -110
  45. data/lib/cinch/isupport.rb +11 -9
  46. data/lib/cinch/logger.rb +168 -0
  47. data/lib/cinch/logger/formatted_logger.rb +72 -55
  48. data/lib/cinch/logger/zcbot_logger.rb +9 -24
  49. data/lib/cinch/logger_list.rb +62 -0
  50. data/lib/cinch/mask.rb +19 -10
  51. data/lib/cinch/message.rb +94 -28
  52. data/lib/cinch/message_queue.rb +70 -28
  53. data/lib/cinch/mode_parser.rb +6 -1
  54. data/lib/cinch/network.rb +104 -0
  55. data/lib/cinch/{rubyext/queue.rb → open_ended_queue.rb} +8 -1
  56. data/lib/cinch/pattern.rb +24 -4
  57. data/lib/cinch/plugin.rb +352 -177
  58. data/lib/cinch/plugin_list.rb +35 -0
  59. data/lib/cinch/rubyext/float.rb +3 -0
  60. data/lib/cinch/rubyext/module.rb +7 -0
  61. data/lib/cinch/rubyext/string.rb +9 -0
  62. data/lib/cinch/sasl.rb +34 -0
  63. data/lib/cinch/sasl/dh_blowfish.rb +71 -0
  64. data/lib/cinch/sasl/diffie_hellman.rb +47 -0
  65. data/lib/cinch/sasl/mechanism.rb +6 -0
  66. data/lib/cinch/sasl/plain.rb +26 -0
  67. data/lib/cinch/storage.rb +62 -0
  68. data/lib/cinch/storage/null.rb +12 -0
  69. data/lib/cinch/storage/yaml.rb +96 -0
  70. data/lib/cinch/syncable.rb +13 -1
  71. data/lib/cinch/target.rb +144 -0
  72. data/lib/cinch/timer.rb +145 -0
  73. data/lib/cinch/user.rb +169 -225
  74. data/lib/cinch/{user_manager.rb → user_list.rb} +7 -2
  75. data/lib/cinch/utilities/deprecation.rb +12 -0
  76. data/lib/cinch/utilities/encoding.rb +54 -0
  77. data/lib/cinch/utilities/kernel.rb +13 -0
  78. data/lib/cinch/utilities/string.rb +13 -0
  79. data/lib/cinch/version.rb +4 -0
  80. metadata +88 -47
  81. data/lib/cinch/logger/logger.rb +0 -44
  82. data/lib/cinch/logger/null_logger.rb +0 -18
  83. data/lib/cinch/rubyext/infinity.rb +0 -1
@@ -0,0 +1,144 @@
1
+ module Cinch
2
+ # @since 2.0.0
3
+ class Target
4
+ include Comparable
5
+
6
+ # @return [String]
7
+ attr_reader :name
8
+ # @return [Bot]
9
+ attr_reader :bot
10
+ def initialize(name, bot)
11
+ @name = name
12
+ @bot = bot
13
+ end
14
+
15
+ # Sends a NOTICE to the target.
16
+ #
17
+ # @param [#to_s] text the message to send
18
+ # @return [void]
19
+ # @see #safe_notice
20
+ def notice(text)
21
+ msg(text, true)
22
+ end
23
+
24
+ # Sends a PRIVMSG to the target.
25
+ #
26
+ # @param [#to_s] text the message to send
27
+ # @param [Boolean] notice Use NOTICE instead of PRIVMSG?
28
+ # @return [void]
29
+ # @see #safe_msg
30
+ def msg(text, notice = false)
31
+ text = text.to_s
32
+ split_start = @bot.config.message_split_start || ""
33
+ split_end = @bot.config.message_split_end || ""
34
+ command = notice ? "NOTICE" : "PRIVMSG"
35
+
36
+ text.split(/\r\n|\r|\n/).each do |line|
37
+ maxlength = 510 - (":" + " #{command} " + " :").size
38
+ maxlength = maxlength - @bot.mask.to_s.length - @name.to_s.length
39
+ maxlength_without_end = maxlength - split_end.bytesize
40
+
41
+ if line.bytesize > maxlength
42
+ splitted = []
43
+
44
+ while line.bytesize > maxlength_without_end
45
+ pos = line.rindex(/\s/, maxlength_without_end)
46
+ r = pos || maxlength_without_end
47
+ splitted << line.slice!(0, r) + split_end.tr(" ", "\u00A0")
48
+ line = split_start.tr(" ", "\u00A0") + line.lstrip
49
+ end
50
+
51
+ splitted << line
52
+ splitted[0, (@bot.config.max_messages || splitted.size)].each do |string|
53
+ string.tr!("\u00A0", " ") # clean string from any non-breaking spaces
54
+ @bot.irc.send("#{command} #@name :#{string}")
55
+ end
56
+ else
57
+ @bot.irc.send("#{command} #@name :#{line}")
58
+ end
59
+ end
60
+ end
61
+ alias_method :send, :msg
62
+ alias_method :privmsg, :msg
63
+
64
+ # Like {#msg}, but remove any non-printable characters from
65
+ # `text`. The purpose of this method is to send text of untrusted
66
+ # sources, like other users or feeds.
67
+ #
68
+ # Note: this will **break** any mIRC color codes embedded in the
69
+ # string.
70
+ #
71
+ # @return (see #msg)
72
+ # @param (see #msg)
73
+ # @see #msg
74
+ # @todo Handle mIRC color codes more gracefully.
75
+ def safe_msg(text, notice = false)
76
+ msg(Cinch::Utilities::String.filter_string(text), notice)
77
+ end
78
+ alias_method :safe_privmsg, :safe_msg
79
+ alias_method :safe_send, :safe_msg
80
+
81
+ # Like {#safe_msg} but for notices.
82
+ #
83
+ # @return (see #safe_msg)
84
+ # @param (see #safe_msg)
85
+ # @see #safe_notice
86
+ # @see #notice
87
+ # @todo (see #safe_msg)
88
+ def safe_notice(text)
89
+ safe_msg(text, true)
90
+ end
91
+
92
+ # Invoke an action (/me) in/to the target.
93
+ #
94
+ # @param [#to_s] text the message to send
95
+ # @return [void]
96
+ # @see #safe_action
97
+ def action(text)
98
+ @bot.irc.send("PRIVMSG #@name :\001ACTION #{text}\001")
99
+ end
100
+
101
+ # Like {#action}, but remove any non-printable characters from
102
+ # `text`. The purpose of this method is to send text from
103
+ # untrusted sources, like other users or feeds.
104
+ #
105
+ # Note: this will **break** any mIRC color codes embedded in the
106
+ # string.
107
+ #
108
+ # @param (see #action)
109
+ # @return (see #action)
110
+ # @see #action
111
+ # @todo Handle mIRC color codes more gracefully.
112
+ def safe_action(text)
113
+ action(Cinch::Utilities::String.filter_string(text))
114
+ end
115
+
116
+ # Send a CTCP to the target.
117
+ #
118
+ # @param [#to_s] message the ctcp message
119
+ # @return [void]
120
+ def ctcp(message)
121
+ send "\001#{message}\001"
122
+ end
123
+
124
+ # @return [Boolean]
125
+ def eql?(other)
126
+ self == other
127
+ end
128
+
129
+ # @param [Target, String]
130
+ # @return [-1, 0, 1, nil]
131
+ def <=>(other)
132
+ casemapping = @bot.irc.isupport["CASEMAPPING"]
133
+ left = @name.irc_downcase(casemapping)
134
+
135
+ if other.is_a?(Target)
136
+ left <=> other.name.irc_downcase(casemapping)
137
+ elsif other.is_a?(String)
138
+ left <=> other.irc_downcase(casemapping)
139
+ else
140
+ nil
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,145 @@
1
+ require "cinch/helpers"
2
+
3
+ module Cinch
4
+ # Timers are used for executing code in the future, either
5
+ # repeatedly or only once.
6
+ #
7
+ # In Cinch, two ways for creating timers are available:
8
+ #
9
+ # - The first way is by declaring them for a plugin, in which case
10
+ # they will start as soon as the bot connects to a server.
11
+ #
12
+ # - The second way is to dynamically create new timers in response
13
+ # to user input. A common example for this is an alarm clock
14
+ # plugin, which has to execute at a specific time.
15
+ #
16
+ # @see Helpers#Timer For dynamically creating timers
17
+ # @see Plugin::ClassMethods#timer For declaring timers in plugins
18
+ # @note It is possible to directly create instances of this class,
19
+ # but the referenced methods should suffice.
20
+ # @since 2.0.0
21
+ class Timer
22
+ include Helpers
23
+
24
+ # @return [Bot]
25
+ attr_reader :bot
26
+
27
+ # @return [Number] The interval (in seconds) of the timer
28
+ attr_accessor :interval
29
+
30
+ # @return [Boolean] If true, each invocation will be
31
+ # executed in a thread of its own.
32
+ attr_accessor :threaded
33
+
34
+ # @return [Proc]
35
+ attr_reader :block
36
+
37
+ # @return [Boolean]
38
+ attr_reader :started
39
+
40
+ # @return [Number] The remaining number of shots before this timer
41
+ # will stop. This value will automatically reset after
42
+ # restarting the timer.
43
+ attr_accessor :shots
44
+ alias_method :threaded?, :threaded
45
+ alias_method :started?, :started
46
+
47
+ # @return [ThreadGroup]
48
+ # @api private
49
+ attr_reader :thread_group
50
+
51
+ # @param [Bot] bot The instance of {Bot} the timer is associated
52
+ # with
53
+ # @option options [Number] :interval The interval (in seconds) of
54
+ # the timer
55
+ # @option options [Number] :shots (Float::INFINITY) How often should the
56
+ # timer fire?
57
+ # @option options [Boolean] :threaded (true) If true, each invocation will be
58
+ # executed in a thread of its own.
59
+ # @option options [Boolean] :start_automatically (true) If true,
60
+ # the timer will automatically start after the bot finished
61
+ # connecting.
62
+ # @option options [Boolean] :stop_automaticall (true) If true, the
63
+ # timer will automatically stop when the bot disconnects.
64
+ def initialize(bot, options, &block)
65
+ options = {:threaded => true, :shots => Float::INFINITY, :start_automatically => true, :stop_automatically => true}.merge(options)
66
+
67
+ @bot = bot
68
+ @interval = options[:interval].to_f
69
+ @threaded = options[:threaded]
70
+ @orig_shots = options[:shots]
71
+ # Setting @shots here so the attr_reader won't return nil
72
+ @shots = @orig_shots
73
+ @block = block
74
+
75
+ @started = false
76
+ @thread_group = ThreadGroup.new
77
+
78
+ if options[:start_automatically]
79
+ @bot.on :connect, //, self do |m, timer|
80
+ timer.start
81
+ end
82
+ end
83
+
84
+ if options[:stop_automatically]
85
+ @bot.on :disconnect, //, self do |m, timer|
86
+ timer.stop
87
+ end
88
+ end
89
+ end
90
+
91
+ # @return [Boolean]
92
+ def stopped?
93
+ !@started
94
+ end
95
+
96
+ # Start the timer
97
+ #
98
+ # @return [void]
99
+ def start
100
+ return if @started
101
+
102
+ @bot.loggers.debug "[timer] Starting timer #{self}"
103
+
104
+ @shots = @orig_shots
105
+
106
+ @thread_group.add Thread.new {
107
+ while @shots > 0 do
108
+ sleep @interval
109
+ if threaded?
110
+ Thread.new do
111
+ rescue_exception do
112
+ @block.call
113
+ end
114
+ end
115
+ else
116
+ rescue_exception do
117
+ @block.call
118
+ end
119
+ end
120
+
121
+ @shots -= 1
122
+ end
123
+ }
124
+
125
+ @started = true
126
+ end
127
+
128
+ # Stop the timer
129
+ #
130
+ # @return [void]
131
+ def stop
132
+ return unless @started
133
+
134
+ @bot.loggers.debug "[timer] Stopping timer #{self}"
135
+
136
+ @thread_group.list.each { |thread| thread.kill }
137
+ @started = false
138
+ end
139
+
140
+ # @return [String]
141
+ def to_s
142
+ "<Cinch::Timer %s/%s shots, %ds interval, %sthreaded, %sstarted, block: %s>" % [@orig_shots - @shots, @orig_shots, @interval, @threaded ? "" : "not ", @started ? "" : "not ", @block]
143
+ end
144
+ end
145
+ end
@@ -1,117 +1,38 @@
1
1
  # -*- coding: utf-8 -*-
2
+ require "cinch/target"
3
+ require "timeout"
4
+
2
5
  module Cinch
3
- class User
6
+ # @attr_reader [String] user
7
+ # @attr_reader [String] host
8
+ # @attr_reader [String] realname
9
+ # @attr_reader [String] authname
10
+ # @attr_reader [Number] idle How long this user has been idle, in seconds.
11
+ # This is a snapshot of the last WHOIS.
12
+ # @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.
16
+ #
17
+ # @version 2.0.0
18
+ class User < Target
4
19
  include Syncable
5
20
 
6
- @users = {} # this will be removed with version 2.0.0
7
- class << self
8
-
9
- # @overload find_ensured(nick, bot)
10
- # Finds or creates a user based on his nick.
11
- #
12
- # @param [String] nick The user's nickname
13
- # @param [Bot] bot An instance of Bot
14
- # @overload find_ensured(user, nick, host, bot)
15
- # Finds or creates a user based on his nick but already
16
- # setting user and host.
17
- #
18
- # @param [String] user The username
19
- # @param [String] nick The nickname
20
- # @param [String] host The user's hostname
21
- # @param [Bot] bot An instance of bot
22
- #
23
- # @return [User]
24
- # @deprecated See {Bot#user_manager} and {UserManager#find_ensured} instead
25
- # @note This method does not work properly if running more than one bot
26
- # @note This method will be removed in Cinch 2.0.0
27
- def find_ensured(*args)
28
- $stderr.puts "Deprecation warning: Beginning with version 1.1.0, User.find_ensured should not be used anymore."
29
- puts caller
30
-
31
- case args.size
32
- when 2
33
- nick = args.first
34
- bot = args.last
35
- bargs = [nick]
36
- when 4
37
- nick = args[1]
38
- bot = args.pop
39
- bargs = args
40
- else
41
- raise ArgumentError
42
- end
43
- downcased_nick = nick.irc_downcase(bot.irc.isupport["CASEMAPPING"])
44
- @users[downcased_nick] = args.last.user_manager.find_ensured(*args[0..-2])
45
- # note: the complete case statement and the assignment to
46
- # @users is only for keeping compatibility with older
47
- # versions, which still use User.find and User.all.
48
- end
49
-
50
- # Finds a user.
51
- #
52
- # @param [String] nick nick of a user
53
- # @return [User, nil]
54
- # @deprecated See {Bot#user_manager} and {UserManager#find} instead
55
- # @note This method does not work properly if running more than one bot
56
- # @note This method will be removed in Cinch 2.0.0
57
- def find(nick)
58
- $stderr.puts "Deprecation warning: Beginning with version 1.1.0, User.find should not be used anymore."
59
- puts caller
60
-
61
- @users[downcased_nick]
62
- end
63
-
64
- # @return [Array<User>] Returns all users
65
- # @deprecated See {Bot#user_manager} and {CacheManager#each} instead
66
- # @note This method does not work properly if running more than one bot
67
- # @note This method will be removed in Cinch 2.0.0
68
- def all
69
- $stderr.puts "Deprecation warning: Beginning with version 1.1.0, User.all should not be used anymore."
70
- puts caller
71
-
72
- @users.values
73
- end
74
- end
75
-
21
+ alias_method :nick, :name
76
22
 
77
23
  # @return [String]
78
- attr_reader :nick
79
- # @return [String]
24
+ # @since 1.1.0
80
25
  attr_reader :last_nick
81
- # @return [Bot]
82
- attr_reader :bot
26
+
83
27
  # @return [Boolean]
84
28
  attr_reader :synced
29
+
85
30
  # @return [Boolean]
86
31
  attr_reader :in_whois
32
+
87
33
  # @api private
88
34
  attr_writer :in_whois
89
35
 
90
- # @return [String]
91
- attr_reader :user
92
- undef_method "user"
93
-
94
- # @return [String]
95
- attr_reader :host
96
- undef_method "host"
97
-
98
- # @return [String]
99
- attr_reader :realname
100
- undef_method "realname"
101
-
102
- # @return [String]
103
- attr_reader :authname
104
- undef_method "authname"
105
-
106
- # @return [Number] How long this user has been idle, in seconds.
107
- # This is a snapshot of the last WHOIS.
108
- attr_reader :idle
109
- undef_method "idle"
110
-
111
- # @return [Time]
112
- attr_reader :signed_on_at
113
- undef_method "signed_on_at"
114
-
115
36
  # @return [Boolean] True if the instance references an user who
116
37
  # cannot be found on the server.
117
38
  attr_reader :unknown
@@ -122,9 +43,17 @@ module Cinch
122
43
  self.unknown?
123
44
  end
124
45
 
125
- # @return [Array<Channel>] All channels the user is in.
126
- attr_reader :channels
127
- undef_method "channels"
46
+ # @return [Boolean] True if the user is online.
47
+ # @note This attribute will be updated by various events, but
48
+ # unless {#monitor} is being used, this information cannot be
49
+ # ensured to be always correct.
50
+ attr_reader :online
51
+ alias_method :online?, :online
52
+ undef_method "online?"
53
+ undef_method "online"
54
+ def online
55
+ self.online?
56
+ end
128
57
 
129
58
  # @return [Boolean] True if the user is using a secure connection, i.e. SSL.
130
59
  attr_reader :secure
@@ -149,6 +78,16 @@ module Cinch
149
78
  # end
150
79
  # @return [Hash]
151
80
  attr_reader :data
81
+
82
+ # @return [Boolean] True if the user is being monitored
83
+ # @see #monitor
84
+ # @see #unmonitor
85
+ attr_reader :monitored
86
+
87
+ # @api private
88
+ attr_writer :monitored
89
+
90
+
152
91
  def initialize(*args)
153
92
  @data = {
154
93
  :user => nil,
@@ -158,14 +97,16 @@ module Cinch
158
97
  :idle => 0,
159
98
  :signed_on_at => nil,
160
99
  :unknown? => false,
100
+ :online? => false,
161
101
  :channels => [],
162
102
  :secure? => false,
103
+ :away => nil,
163
104
  }
164
105
  case args.size
165
106
  when 2
166
- @nick, @bot = args
107
+ @name, @bot = args
167
108
  when 4
168
- @data[:user], @nick, @data[:host], @bot = args
109
+ @data[:user], @name, @data[:host], @bot = args
169
110
  else
170
111
  raise ArgumentError
171
112
  end
@@ -173,29 +114,26 @@ module Cinch
173
114
  @synced_attributes = Set.new
174
115
 
175
116
  @when_requesting_synced_attribute = lambda {|attr|
176
- unless @synced
117
+ unless synced?(attr)
177
118
  @data[:unknown?] = false
178
119
  unsync :unknown?
179
120
 
180
- unsync attr
181
121
  whois
182
122
  end
183
123
  }
124
+
125
+ @monitored = false
184
126
  end
185
127
 
186
128
  # Checks if the user is identified. Currently officially supports
187
129
  # Quakenet and Freenode.
188
130
  #
189
131
  # @return [Boolean] true if the user is identified
132
+ # @version 1.1.0
190
133
  def authed?
191
134
  !attr(:authname).nil?
192
135
  end
193
136
 
194
- # @see Syncable#attr
195
- def attr(attribute, data = true, unsync = false)
196
- super
197
- end
198
-
199
137
  # Queries the IRC server for information on the user. This will
200
138
  # set the User's state to not synced. After all information are
201
139
  # received, the object will be set back to synced.
@@ -203,13 +141,16 @@ module Cinch
203
141
  # @return [void]
204
142
  def whois
205
143
  return if @in_whois
206
- @synced = false
207
144
  @data.keys.each do |attr|
208
145
  unsync attr
209
146
  end
210
147
 
211
148
  @in_whois = true
212
- @bot.raw "WHOIS #@nick #@nick"
149
+ if @bot.irc.network.whois_only_one_argument?
150
+ @bot.irc.send "WHOIS #@name"
151
+ else
152
+ @bot.irc.send "WHOIS #@name #@name"
153
+ end
213
154
  end
214
155
  alias_method :refresh, :whois
215
156
 
@@ -224,6 +165,7 @@ module Cinch
224
165
  @in_whois = false
225
166
  if not_found
226
167
  sync(:unknown?, true, true)
168
+ self.online = false
227
169
  sync(:idle, 0, true)
228
170
  sync(:channels, [], true)
229
171
 
@@ -257,7 +199,7 @@ module Cinch
257
199
  end
258
200
 
259
201
  sync(:unknown?, false, true)
260
- @synced = true
202
+ self.online = true
261
203
  end
262
204
 
263
205
  # @return [void]
@@ -265,101 +207,17 @@ module Cinch
265
207
  # @api private
266
208
  # @see Syncable#unsync_all
267
209
  def unsync_all
268
- @synced = false
269
210
  super
270
211
  end
271
212
 
272
- # @group Sending messages
273
-
274
- # Send a message to the user.
275
- #
276
- # @param [String] message the message
277
- # @return [void]
278
- def send(message)
279
- @bot.msg(@nick, message)
280
- end
281
- alias_method :privmsg, :send
282
- alias_method :msg, :send
283
-
284
- # Send a notice to the user.
285
- #
286
- # @param [String] message the message
287
- # @return [void]
288
- def notice(message)
289
- @bot.notice(@nick, message)
290
- end
291
-
292
- # Like {#safe_send} but for notices.
293
- #
294
- # @param (see #safe_send)
295
- # @return (see #safe_send)
296
- # @see #safe_send
297
- # @todo (see #safe_send)
298
- def safe_notice(message)
299
- @bot.safe_notice(@nick, message)
300
- end
301
-
302
- # Send a message to the user, but remove any non-printable
303
- # characters. The purpose of this method is to send text from
304
- # untrusted sources, like other users or feeds.
305
- #
306
- # Note: this will **break** any mIRC color codes embedded in the
307
- # string.
308
- #
309
- # @param (see #send)
310
- # @return (see #send)
311
- # @see #send
312
- # @see Bot#safe_msg
313
- # @todo Handle mIRC color codes more gracefully.
314
- def safe_send(message)
315
- @bot.safe_msg(@nick, message)
316
- end
317
- alias_method :safe_privmsg, :safe_send
318
- alias_method :safe_msg, :safe_send
319
-
320
- # Send a CTCP to the user.
321
- #
322
- # @param [String] message the ctcp message
323
- # @return [void]
324
- def ctcp(message)
325
- send "\001#{message}\001"
326
- end
327
-
328
- # Send an action (/me) to the user.
329
- #
330
- # @param [String] message the message
331
- # @return [void]
332
- # @see #safe_action
333
- def action(message)
334
- @bot.action(@name, message)
335
- end
336
-
337
- # Send an action (/me) to the user but remove any non-printable
338
- # characters. The purpose of this method is to send text from
339
- # untrusted sources, like other users or feeds.
340
- #
341
- # Note: this will **break** any mIRC color codes embedded in the
342
- # string.
343
- #
344
- # @param (see #action)
345
- # @return (see #action)
346
- # @see #action
347
- # @see Bot#safe_action
348
- # @todo Handle mIRC color codes more gracefully.
349
- def safe_action(message)
350
- @bot.safe_action(@name, message)
351
- end
352
-
353
- # @endgroup
354
-
355
213
  # @return [String]
356
214
  def to_s
357
- @nick
215
+ @name
358
216
  end
359
217
 
360
218
  # @return [String]
361
219
  def inspect
362
- "#<User nick=#{@nick.inspect}>"
220
+ "#<User nick=#{@name.inspect}>"
363
221
  end
364
222
 
365
223
  # Generates a mask for the user.
@@ -377,7 +235,7 @@ module Cinch
377
235
  s = s.gsub(/%(.)/) {
378
236
  case $1
379
237
  when "n"
380
- @nick
238
+ @name
381
239
  when "u"
382
240
  self.user
383
241
  when "h"
@@ -392,10 +250,115 @@ module Cinch
392
250
  Mask.new(s)
393
251
  end
394
252
 
253
+ # Check if the user matches a mask.
254
+ #
255
+ # @param [Ban, Mask, User, String] other The user or mask to match against
256
+ # @return [Boolean]
257
+ def match(other)
258
+ Mask.from(other) =~ Mask.from(self)
259
+ end
260
+ alias_method :=~, :match
261
+
262
+ # Starts monitoring a user's online state by either using MONITOR
263
+ # or periodically running WHOIS.
264
+ #
265
+ # @since 2.0.0
266
+ # @return [void]
267
+ # @see #unmonitor
268
+ def monitor
269
+ if @bot.irc.isupport["MONITOR"] > 0
270
+ @bot.irc.send "MONITOR + #@name"
271
+ else
272
+ refresh
273
+ @monitored_timer = Timer.new(@bot, interval: 30) {
274
+ refresh
275
+ }.start
276
+ end
277
+
278
+ @monitored = true
279
+ end
280
+
281
+ # Stops monitoring a user's online state.
282
+ #
283
+ # @since 2.0.0
284
+ # @return [void]
285
+ # @see #monitor
286
+ def unmonitor
287
+ if @bot.irc.isupport["MONITOR"] > 0
288
+ @bot.irc.send "MONITOR - #@name"
289
+ else
290
+ @monitored_timer.stop
291
+ end
292
+
293
+ @monitored = false
294
+ end
295
+
296
+ # Send data via DCC SEND to a user.
297
+ #
298
+ # @param [DCC::DCCableObject] io
299
+ # @param [String] filename
300
+ # @since 2.0.0
301
+ # @return [void]
302
+ # @note This method blocks.
303
+ def dcc_send(io, filename = File.basename(io.path))
304
+ own_ip = bot.config.dcc.own_ip || @bot.irc.socket.addr[2]
305
+ dcc = DCC::Outgoing::Send.new(receiver: self,
306
+ filename: filename,
307
+ io: io,
308
+ own_ip: own_ip
309
+ )
310
+
311
+ dcc.start_server
312
+
313
+ handler = Handler.new(@bot, :message,
314
+ Pattern.new(/^/,
315
+ /\001DCC RESUME #{filename} #{dcc.port} (\d+)\001/,
316
+ /$/)) do |m, position|
317
+ next unless m.user == self
318
+ dcc.seek(position.to_i)
319
+ m.user.send "\001DCC ACCEPT #{filename} #{dcc.port} #{position}\001"
320
+
321
+ handler.unregister
322
+ end
323
+ @bot.handlers.register(handler)
324
+
325
+ @bot.loggers.info "DCC: Outgoing DCC SEND: File name: %s - Size: %dB - IP: %s - Port: %d - Status: waiting" % [filename, io.size, own_ip, dcc.port]
326
+ dcc.send_handshake
327
+ begin
328
+ dcc.listen
329
+ @bot.loggers.info "DCC: Outgoing DCC SEND: File name: %s - Size: %dB - IP: %s - Port: %d - Status: done" % [filename, io.size, own_ip, dcc.port]
330
+ rescue Timeout::Error
331
+ @bot.loggers.info "DCC: Outgoing DCC SEND: File name: %s - Size: %dB - IP: %s - Port: %d - Status: failed (timeout)" % [filename, io.size, own_ip, dcc.port]
332
+ ensure
333
+ handler.unregister
334
+ end
335
+ end
336
+
337
+ # Updates the user's online state and dispatch the correct event.
338
+ #
339
+ # @since 2.0.0
340
+ # @return [void]
341
+ # @api private
342
+ def online=(bool)
343
+ notify = self.__send__("online?_unsynced") != bool && @monitored
344
+ sync(:online?, bool, true)
345
+
346
+ return unless notify
347
+ if bool
348
+ @bot.handlers.dispatch(:online, nil, self)
349
+ else
350
+ @bot.handlers.dispatch(:offline, nil, self)
351
+ end
352
+ end
353
+
354
+ # Used to update the user's nick on nickchange events.
355
+ #
356
+ # @param [String] new_nick The user's new nick
395
357
  # @api private
358
+ # @return [void]
396
359
  def update_nick(new_nick)
397
- @last_nick, @nick = @nick, new_nick
398
- @bot.user_manager.update_nick(self)
360
+ @last_nick, @name = @name, new_nick
361
+ @bot.user_list.update_nick(self)
399
362
  end
400
363
 
401
364
  # Provides synced access to user attributes.
@@ -406,12 +369,13 @@ module Cinch
406
369
  end
407
370
 
408
371
  if @data.has_key?(m)
409
- attr(m, true, unsync = false)
372
+ attr(m, true, unsync)
410
373
  else
411
374
  super
412
375
  end
413
376
  end
414
377
 
378
+ # @since 1.1.2
415
379
  def respond_to?(m)
416
380
  if m.to_s =~ /^(.+)_unsynced$/
417
381
  m = $1.to_sym
@@ -419,25 +383,5 @@ module Cinch
419
383
 
420
384
  return @data.has_key?(m) || super
421
385
  end
422
-
423
- # @return [Boolean]
424
- def ==(other)
425
- return case other
426
- when self.class
427
- @nick == other.nick
428
- when String
429
- self.to_s == other
430
- when Bot
431
- self.nick == other.config.nick
432
- else
433
- false
434
- end
435
- end
436
- alias_method :eql?, "=="
437
-
438
- # @return [Fixnum]
439
- def hash
440
- @nick.hash
441
- end
442
386
  end
443
387
  end