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.
- data/LICENSE +1 -0
- data/README.md +3 -3
- data/docs/bot_options.md +435 -0
- data/docs/changes.md +440 -0
- data/docs/common_mistakes.md +35 -0
- data/docs/common_tasks.md +47 -0
- data/docs/encodings.md +67 -0
- data/docs/events.md +272 -0
- data/docs/logging.md +5 -0
- data/docs/migrating.md +267 -0
- data/docs/readme.md +18 -0
- data/examples/plugins/custom_prefix.rb +1 -1
- data/examples/plugins/dice_roll.rb +38 -0
- data/examples/plugins/lambdas.rb +1 -1
- data/examples/plugins/memo.rb +16 -10
- data/examples/plugins/url_shorten.rb +1 -0
- data/lib/cinch.rb +5 -60
- data/lib/cinch/ban.rb +13 -7
- data/lib/cinch/bot.rb +228 -403
- data/lib/cinch/{cache_manager.rb → cached_list.rb} +5 -1
- data/lib/cinch/callback.rb +3 -0
- data/lib/cinch/channel.rb +119 -195
- data/lib/cinch/{channel_manager.rb → channel_list.rb} +6 -3
- data/lib/cinch/configuration.rb +73 -0
- data/lib/cinch/configuration/bot.rb +47 -0
- data/lib/cinch/configuration/dcc.rb +16 -0
- data/lib/cinch/configuration/plugins.rb +41 -0
- data/lib/cinch/configuration/sasl.rb +17 -0
- data/lib/cinch/configuration/ssl.rb +19 -0
- data/lib/cinch/configuration/storage.rb +37 -0
- data/lib/cinch/configuration/timeouts.rb +14 -0
- data/lib/cinch/constants.rb +531 -369
- 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 +131 -0
- data/lib/cinch/dcc/outgoing.rb +1 -0
- data/lib/cinch/dcc/outgoing/send.rb +115 -0
- data/lib/cinch/exceptions.rb +8 -1
- data/lib/cinch/formatting.rb +106 -0
- data/lib/cinch/handler.rb +104 -0
- data/lib/cinch/handler_list.rb +86 -0
- data/lib/cinch/helpers.rb +167 -10
- data/lib/cinch/irc.rb +525 -110
- data/lib/cinch/isupport.rb +11 -9
- data/lib/cinch/logger.rb +168 -0
- data/lib/cinch/logger/formatted_logger.rb +72 -55
- data/lib/cinch/logger/zcbot_logger.rb +9 -24
- data/lib/cinch/logger_list.rb +62 -0
- data/lib/cinch/mask.rb +19 -10
- data/lib/cinch/message.rb +94 -28
- data/lib/cinch/message_queue.rb +70 -28
- data/lib/cinch/mode_parser.rb +6 -1
- data/lib/cinch/network.rb +104 -0
- data/lib/cinch/{rubyext/queue.rb → open_ended_queue.rb} +8 -1
- data/lib/cinch/pattern.rb +24 -4
- data/lib/cinch/plugin.rb +352 -177
- data/lib/cinch/plugin_list.rb +35 -0
- data/lib/cinch/rubyext/float.rb +3 -0
- data/lib/cinch/rubyext/module.rb +7 -0
- data/lib/cinch/rubyext/string.rb +9 -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/storage.rb +62 -0
- data/lib/cinch/storage/null.rb +12 -0
- data/lib/cinch/storage/yaml.rb +96 -0
- data/lib/cinch/syncable.rb +13 -1
- data/lib/cinch/target.rb +144 -0
- data/lib/cinch/timer.rb +145 -0
- data/lib/cinch/user.rb +169 -225
- data/lib/cinch/{user_manager.rb → user_list.rb} +7 -2
- data/lib/cinch/utilities/deprecation.rb +12 -0
- data/lib/cinch/utilities/encoding.rb +54 -0
- data/lib/cinch/utilities/kernel.rb +13 -0
- data/lib/cinch/utilities/string.rb +13 -0
- data/lib/cinch/version.rb +4 -0
- metadata +88 -47
- data/lib/cinch/logger/logger.rb +0 -44
- data/lib/cinch/logger/null_logger.rb +0 -18
- data/lib/cinch/rubyext/infinity.rb +0 -1
data/lib/cinch/target.rb
ADDED
@@ -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
|
data/lib/cinch/timer.rb
ADDED
@@ -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
|
data/lib/cinch/user.rb
CHANGED
@@ -1,117 +1,38 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
|
+
require "cinch/target"
|
3
|
+
require "timeout"
|
4
|
+
|
2
5
|
module Cinch
|
3
|
-
|
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
|
-
|
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
|
-
|
79
|
-
# @return [String]
|
24
|
+
# @since 1.1.0
|
80
25
|
attr_reader :last_nick
|
81
|
-
|
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 [
|
126
|
-
|
127
|
-
|
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
|
-
@
|
107
|
+
@name, @bot = args
|
167
108
|
when 4
|
168
|
-
@data[:user], @
|
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
|
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.
|
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
|
-
|
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
|
-
@
|
215
|
+
@name
|
358
216
|
end
|
359
217
|
|
360
218
|
# @return [String]
|
361
219
|
def inspect
|
362
|
-
"#<User nick=#{@
|
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
|
-
@
|
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, @
|
398
|
-
@bot.
|
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
|
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
|