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.
- 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
|