cinch 1.1.3 → 2.0.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
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