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/docs/readme.md
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# @title README
|
2
|
+
|
3
|
+
|
4
|
+
- {file:changes.md Changelog}
|
5
|
+
- {file:migrating.md Migration guides}
|
6
|
+
|
7
|
+
# Important documents
|
8
|
+
|
9
|
+
The following is a list of important documents to read.
|
10
|
+
|
11
|
+
- {file:bot_options.md Bot options}
|
12
|
+
- {file:common_tasks.md Common Tasks}
|
13
|
+
- {file:common_mistakes.md Common mistakes}
|
14
|
+
- {Cinch::DCC DCC}
|
15
|
+
- {file:encodings.md Encodings}
|
16
|
+
- {file:logging.md Logging}
|
17
|
+
- {Cinch::SASL SASL}
|
18
|
+
- {file:events.d Events}
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'cinch'
|
2
|
+
|
3
|
+
class DiceRoll
|
4
|
+
include Cinch::Plugin
|
5
|
+
|
6
|
+
# [[<repeats>#]<rolls>]d<sides>[<+/-><offset>]
|
7
|
+
match(/roll (?:(?:(\d+)#)?(\d+))?d(\d+)(?:([+-])(\d+))?/)
|
8
|
+
def execute(m, repeats, rolls, sides, offset_op, offset)
|
9
|
+
repeats = repeats.to_i
|
10
|
+
repeats = 1 if repeats < 1
|
11
|
+
rolls = rolls.to_i
|
12
|
+
rolls = 1 if rolls < 1
|
13
|
+
|
14
|
+
total = 0
|
15
|
+
|
16
|
+
repeats.times do
|
17
|
+
rolls.times do
|
18
|
+
score = rand(sides.to_i) + 1
|
19
|
+
if offset_op
|
20
|
+
score = score.send(offset_op, offset.to_i)
|
21
|
+
end
|
22
|
+
total += score
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
m.reply "Your dice roll was: #{total}", true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
bot = Cinch::Bot.new do
|
31
|
+
configure do |c|
|
32
|
+
c.server = 'irc.freenode.org'
|
33
|
+
c.channels = ['#cinch-bots']
|
34
|
+
c.plugins.plugins = [DiceRoll]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
bot.start
|
data/examples/plugins/lambdas.rb
CHANGED
@@ -9,7 +9,7 @@ class DirectAddressing
|
|
9
9
|
#
|
10
10
|
# The reason we are using a lambda is that the bot's nick can change
|
11
11
|
# and the prefix has to be up to date.
|
12
|
-
prefix lambda{ |m| m.bot.nick + ": " }
|
12
|
+
prefix lambda{ |m| Regexp.new("^" + Regexp.escape(m.bot.nick + ": " ))}
|
13
13
|
|
14
14
|
match "hello", method: :greet
|
15
15
|
def greet(m)
|
data/examples/plugins/memo.rb
CHANGED
@@ -1,39 +1,42 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require 'cinch'
|
4
|
+
require 'cinch/storage/yaml'
|
4
5
|
|
5
6
|
class Memo
|
6
|
-
class MemoStruct < Struct.new(:
|
7
|
+
class MemoStruct < Struct.new(:user, :channel, :text, :time)
|
7
8
|
def to_s
|
8
|
-
"[#{time.asctime}] <#{channel}/#{
|
9
|
+
"[#{time.asctime}] <#{channel}/#{user}> #{text}"
|
9
10
|
end
|
10
11
|
end
|
11
12
|
|
12
13
|
include Cinch::Plugin
|
13
14
|
|
14
|
-
listen_to :message
|
15
|
-
match /memo (.+?) (.+)/
|
16
|
-
|
17
15
|
def initialize(*args)
|
18
16
|
super
|
19
|
-
|
17
|
+
storage[:memos] ||= {}
|
20
18
|
end
|
21
19
|
|
20
|
+
listen_to :message
|
21
|
+
match /memo (.+?) (.+)/
|
22
|
+
|
22
23
|
def listen(m)
|
23
|
-
if
|
24
|
-
m.user.send
|
24
|
+
if storage[:memos].has_key?(m.user.nick)
|
25
|
+
m.user.send storage[:memos].delete(m.user.nick).to_s
|
26
|
+
storage.save
|
25
27
|
end
|
26
28
|
end
|
27
29
|
|
28
30
|
def execute(m, nick, message)
|
29
|
-
if
|
31
|
+
if storage[:memos].key?(nick)
|
30
32
|
m.reply "There's already a memo for #{nick}. You can only store one right now"
|
31
33
|
elsif nick == m.user.nick
|
32
34
|
m.reply "You can't leave memos for yourself.."
|
33
35
|
elsif nick == bot.nick
|
34
36
|
m.reply "You can't leave memos for me.."
|
35
37
|
else
|
36
|
-
|
38
|
+
storage[:memos][nick] = MemoStruct.new(m.user.name, m.channel.name, message, Time.now)
|
39
|
+
storage.save
|
37
40
|
m.reply "Added memo for #{nick}"
|
38
41
|
end
|
39
42
|
end
|
@@ -44,6 +47,9 @@ bot = Cinch::Bot.new do
|
|
44
47
|
c.server = "irc.freenode.org"
|
45
48
|
c.channels = ["#cinch-bots"]
|
46
49
|
c.plugins.plugins = [Memo]
|
50
|
+
c.storage.backend = Cinch::Storage::YAML
|
51
|
+
c.storage.basedir = "./yaml/"
|
52
|
+
c.storage.autosave = true
|
47
53
|
end
|
48
54
|
end
|
49
55
|
|
data/lib/cinch.rb
CHANGED
@@ -1,61 +1,6 @@
|
|
1
|
+
require 'cinch/version'
|
2
|
+
require 'cinch/utilities/kernel'
|
3
|
+
require 'cinch/utilities/string'
|
4
|
+
require 'cinch/utilities/deprecation'
|
5
|
+
require 'cinch/utilities/encoding'
|
1
6
|
require 'cinch/bot'
|
2
|
-
|
3
|
-
module Cinch
|
4
|
-
VERSION = '1.1.3'
|
5
|
-
|
6
|
-
# @return [String]
|
7
|
-
# @todo Handle mIRC color codes more gracefully.
|
8
|
-
# @api private
|
9
|
-
def self.filter_string(string)
|
10
|
-
string.gsub(/[\x00-\x1f]/, '')
|
11
|
-
end
|
12
|
-
|
13
|
-
# @api private
|
14
|
-
def self.encode_incoming(string, encoding)
|
15
|
-
string = string.dup
|
16
|
-
if encoding == :irc
|
17
|
-
# If incoming text is valid UTF-8, it will be interpreted as
|
18
|
-
# such. If it fails validation, a CP1252 -> UTF-8 conversion
|
19
|
-
# is performed. This allows you to see non-ASCII from mIRC
|
20
|
-
# users (non-UTF-8) and other users sending you UTF-8.
|
21
|
-
#
|
22
|
-
# (from http://xchat.org/encoding/#hybrid)
|
23
|
-
string.force_encoding("UTF-8")
|
24
|
-
if !string.valid_encoding?
|
25
|
-
string.force_encoding("CP1252").encode!("UTF-8", {:invalid => :replace, :undef => :replace})
|
26
|
-
end
|
27
|
-
else
|
28
|
-
string.force_encoding(encoding).encode!({:invalid => :replace, :undef => :replace})
|
29
|
-
string = string.chars.select { |c| c.valid_encoding? }.join
|
30
|
-
end
|
31
|
-
|
32
|
-
return string
|
33
|
-
end
|
34
|
-
|
35
|
-
# @api private
|
36
|
-
def self.encode_outgoing(string, encoding)
|
37
|
-
string = string.dup
|
38
|
-
if encoding == :irc
|
39
|
-
# If your text contains only characters that fit inside the CP1252
|
40
|
-
# code page (aka Windows Latin-1), the entire line will be sent
|
41
|
-
# that way. mIRC users should see it correctly. XChat users who
|
42
|
-
# are using UTF-8 will also see it correctly, because it will fail
|
43
|
-
# UTF-8 validation and will be assumed to be CP1252, even by older
|
44
|
-
# XChat versions.
|
45
|
-
#
|
46
|
-
# If the text doesn't fit inside the CP1252 code page, (for eaxmple if you
|
47
|
-
# type Eastern European characters, or Russian) it will be sent as UTF-8. Only
|
48
|
-
# UTF-8 capable clients will be able to see these characters correctly
|
49
|
-
#
|
50
|
-
# (from http://xchat.org/encoding/#hybrid)
|
51
|
-
begin
|
52
|
-
string.encode!("CP1252")
|
53
|
-
rescue Encoding::UndefinedConversionError
|
54
|
-
end
|
55
|
-
else
|
56
|
-
string.encode!(encoding, {:invalid => :replace, :undef => :replace})
|
57
|
-
end
|
58
|
-
|
59
|
-
return string
|
60
|
-
end
|
61
|
-
end
|
data/lib/cinch/ban.rb
CHANGED
@@ -1,10 +1,16 @@
|
|
1
1
|
require "cinch/mask"
|
2
2
|
module Cinch
|
3
|
+
# This class represents channel bans.
|
3
4
|
class Ban
|
4
|
-
# @return [Mask
|
5
|
+
# @return [Mask] A {Mask} object for non-extended bans
|
6
|
+
# @return [String] A String object for extended bans (see {#extended})
|
5
7
|
attr_reader :mask
|
6
8
|
|
7
|
-
#
|
9
|
+
# The user who created the ban. Might be nil on networks that do
|
10
|
+
# not strictly follow the RFCs, for example IRCnet in some(?)
|
11
|
+
# cases.
|
12
|
+
#
|
13
|
+
# @return [User, nil] The user who created the ban
|
8
14
|
attr_reader :by
|
9
15
|
|
10
16
|
# @return [Time]
|
@@ -13,17 +19,17 @@ module Cinch
|
|
13
19
|
# @return [Boolean] whether this is an extended ban (as used by for example Freenode)
|
14
20
|
attr_reader :extended
|
15
21
|
|
16
|
-
# @param [String] mask The mask
|
17
|
-
# @param [User] by The user who created the ban
|
22
|
+
# @param [String, Mask] mask The mask
|
23
|
+
# @param [User, nil] by The user who created the ban.
|
18
24
|
# @param [Time] at The time at which the ban was created
|
19
25
|
def initialize(mask, by, at)
|
20
26
|
@by, @created_at = by, at
|
21
|
-
if mask =~
|
27
|
+
if mask =~ /^[\$~]/
|
22
28
|
@extended = true
|
23
29
|
@mask = mask
|
24
30
|
else
|
25
31
|
@extended = false
|
26
|
-
@mask = Mask.
|
32
|
+
@mask = Mask.from(mask)
|
27
33
|
end
|
28
34
|
end
|
29
35
|
|
@@ -31,7 +37,7 @@ module Cinch
|
|
31
37
|
# @raise [Exceptions::UnsupportedFeature] Cinch does not support
|
32
38
|
# Freenode's extended bans
|
33
39
|
def match(user)
|
34
|
-
raise UnsupportedFeature, "extended bans
|
40
|
+
raise UnsupportedFeature, "extended bans are not supported yet" if @extended
|
35
41
|
@mask =~ user
|
36
42
|
end
|
37
43
|
alias_method :=~, :match
|
data/lib/cinch/bot.rb
CHANGED
@@ -3,20 +3,23 @@ require 'socket'
|
|
3
3
|
require "thread"
|
4
4
|
require "ostruct"
|
5
5
|
require "cinch/rubyext/module"
|
6
|
-
require "cinch/rubyext/queue"
|
7
6
|
require "cinch/rubyext/string"
|
8
|
-
require "cinch/rubyext/
|
7
|
+
require "cinch/rubyext/float"
|
9
8
|
|
10
9
|
require "cinch/exceptions"
|
11
10
|
|
11
|
+
require "cinch/handler"
|
12
12
|
require "cinch/helpers"
|
13
|
-
|
14
|
-
require "cinch/
|
13
|
+
|
14
|
+
require "cinch/logger_list"
|
15
|
+
require "cinch/logger"
|
16
|
+
|
15
17
|
require "cinch/logger/formatted_logger"
|
16
18
|
require "cinch/syncable"
|
17
19
|
require "cinch/message"
|
18
20
|
require "cinch/message_queue"
|
19
21
|
require "cinch/irc"
|
22
|
+
require "cinch/target"
|
20
23
|
require "cinch/channel"
|
21
24
|
require "cinch/user"
|
22
25
|
require "cinch/constants"
|
@@ -27,81 +30,103 @@ require "cinch/isupport"
|
|
27
30
|
require "cinch/plugin"
|
28
31
|
require "cinch/pattern"
|
29
32
|
require "cinch/mode_parser"
|
30
|
-
require "cinch/
|
31
|
-
require "cinch/
|
32
|
-
|
33
|
+
require "cinch/dcc"
|
34
|
+
require "cinch/sasl"
|
35
|
+
|
36
|
+
require "cinch/handler_list"
|
37
|
+
require "cinch/cached_list"
|
38
|
+
require "cinch/channel_list"
|
39
|
+
require "cinch/user_list"
|
40
|
+
require "cinch/plugin_list"
|
41
|
+
|
42
|
+
require "cinch/timer"
|
43
|
+
require "cinch/formatting"
|
44
|
+
|
45
|
+
require "cinch/configuration"
|
46
|
+
require "cinch/configuration/bot"
|
47
|
+
require "cinch/configuration/plugins"
|
48
|
+
require "cinch/configuration/ssl"
|
49
|
+
require "cinch/configuration/timeouts"
|
50
|
+
require "cinch/configuration/storage"
|
51
|
+
require "cinch/configuration/dcc"
|
52
|
+
require "cinch/configuration/sasl"
|
33
53
|
|
34
54
|
module Cinch
|
55
|
+
# @attr nick
|
56
|
+
# @version 2.0.0
|
57
|
+
class Bot < User
|
58
|
+
include Helpers
|
59
|
+
|
35
60
|
|
36
|
-
|
37
|
-
# @
|
61
|
+
# @return [Configuration::Bot]
|
62
|
+
# @version 2.0.0
|
38
63
|
attr_reader :config
|
64
|
+
|
65
|
+
# The underlying IRC connection
|
66
|
+
#
|
39
67
|
# @return [IRC]
|
40
68
|
attr_reader :irc
|
41
|
-
|
42
|
-
|
69
|
+
|
70
|
+
# The logger list containing all loggers
|
71
|
+
#
|
72
|
+
# @return [LoggerList]
|
73
|
+
# @since 2.0.0
|
74
|
+
attr_accessor :loggers
|
75
|
+
|
43
76
|
# @return [Array<Channel>] All channels the bot currently is in
|
44
77
|
attr_reader :channels
|
45
|
-
|
46
|
-
|
47
|
-
# @
|
48
|
-
attr_reader :mask
|
49
|
-
# @return [String]
|
50
|
-
attr_reader :user
|
51
|
-
# @return [String]
|
52
|
-
attr_reader :realname
|
53
|
-
# @return [Time]
|
54
|
-
attr_reader :signed_on_at
|
55
|
-
# @return [Array<Plugin>] All registered plugins
|
78
|
+
|
79
|
+
# @return [PluginList] All registered plugins
|
80
|
+
# @version 2.0.0
|
56
81
|
attr_reader :plugins
|
57
|
-
|
58
|
-
# @api private
|
59
|
-
attr_reader :handler_threads
|
82
|
+
|
60
83
|
# @return [Boolean] whether the bot is in the process of disconnecting
|
61
84
|
attr_reader :quitting
|
62
|
-
|
63
|
-
|
64
|
-
# @
|
65
|
-
|
85
|
+
|
86
|
+
# @return [UserList] All {User users} the bot knows about.
|
87
|
+
# @see UserList
|
88
|
+
# @since 1.1.0
|
89
|
+
attr_reader :user_list
|
90
|
+
|
91
|
+
# @return [ChannelList] All {Channel channels} the bot knows about.
|
92
|
+
# @see ChannelList
|
93
|
+
# @since 1.1.0
|
94
|
+
attr_reader :channel_list
|
95
|
+
|
96
|
+
# @return [PluginList] All loaded plugins.
|
97
|
+
# @version 2.0.0
|
98
|
+
attr_reader :plugins
|
99
|
+
|
66
100
|
# @return [Boolean]
|
67
101
|
# @api private
|
68
102
|
attr_accessor :last_connection_was_successful
|
69
103
|
|
70
|
-
# @
|
104
|
+
# @return [Callback]
|
105
|
+
# @api private
|
106
|
+
attr_reader :callback
|
71
107
|
|
72
|
-
#
|
108
|
+
# The {HandlerList}, providing access to all registered plugins
|
109
|
+
# and plugin manipulation as well as {HandlerList#dispatch calling handlers}.
|
73
110
|
#
|
74
|
-
# @
|
75
|
-
# @
|
76
|
-
# @
|
77
|
-
|
78
|
-
# Channel(target).join
|
79
|
-
# end
|
80
|
-
def Channel(channel)
|
81
|
-
return channel if channel.is_a?(Channel)
|
82
|
-
@channel_manager.find_ensured(channel)
|
83
|
-
end
|
111
|
+
# @return [HandlerList]
|
112
|
+
# @see HandlerList
|
113
|
+
# @since 2.0.0
|
114
|
+
attr_reader :handlers
|
84
115
|
|
85
|
-
#
|
116
|
+
# The bot's modes.
|
86
117
|
#
|
87
|
-
# @
|
88
|
-
# @
|
89
|
-
|
90
|
-
|
91
|
-
#
|
92
|
-
# m.reply "%s is named %s and connects from %s" % [user.nick, user.name, user.host]
|
93
|
-
# end
|
94
|
-
def User(user)
|
95
|
-
return user if user.is_a?(User)
|
96
|
-
@user_manager.find_ensured(user)
|
97
|
-
end
|
118
|
+
# @return [Array<String>]
|
119
|
+
# @since 2.0.0
|
120
|
+
attr_reader :modes
|
121
|
+
|
122
|
+
# @group Helper methods
|
98
123
|
|
99
124
|
# Define helper methods in the context of the bot.
|
100
125
|
#
|
101
126
|
# @yield Expects a block containing method definitions
|
102
127
|
# @return [void]
|
103
128
|
def helpers(&b)
|
104
|
-
|
129
|
+
@callback.instance_eval(&b)
|
105
130
|
end
|
106
131
|
|
107
132
|
# Since Cinch uses threads, all handlers can be run
|
@@ -142,234 +167,47 @@ module Cinch
|
|
142
167
|
semaphore.synchronize(&block)
|
143
168
|
end
|
144
169
|
|
145
|
-
# Stop execution of the current {#on} handler.
|
146
|
-
#
|
147
|
-
# @return [void]
|
148
|
-
def halt
|
149
|
-
throw :halt
|
150
|
-
end
|
151
|
-
|
152
170
|
# @endgroup
|
153
|
-
# @group Sending messages
|
154
171
|
|
155
|
-
# Sends a raw message to the server.
|
156
|
-
#
|
157
|
-
# @param [String] command The message to send.
|
158
|
-
# @return [void]
|
159
|
-
# @see IRC#message
|
160
|
-
def raw(command)
|
161
|
-
@irc.message(command)
|
162
|
-
end
|
163
|
-
|
164
|
-
# Sends a PRIVMSG to a recipient (a channel or user).
|
165
|
-
# You should be using {Channel#send} and {User#send} instead.
|
166
|
-
#
|
167
|
-
# @param [String] recipient the recipient
|
168
|
-
# @param [String] text the message to send
|
169
|
-
# @param [Boolean] notice Use NOTICE instead of PRIVMSG?
|
170
|
-
# @return [void]
|
171
|
-
# @see Channel#send
|
172
|
-
# @see User#send
|
173
|
-
# @see #safe_msg
|
174
|
-
def msg(recipient, text, notice = false)
|
175
|
-
text = text.to_s
|
176
|
-
split_start = @config.message_split_start || ""
|
177
|
-
split_end = @config.message_split_end || ""
|
178
|
-
command = notice ? "NOTICE" : "PRIVMSG"
|
179
|
-
|
180
|
-
text.split(/\r\n|\r|\n/).each do |line|
|
181
|
-
maxlength = 510 - (":" + " #{command} " + " :").size
|
182
|
-
maxlength = maxlength - self.mask.to_s.length - recipient.to_s.length
|
183
|
-
maxlength_without_end = maxlength - split_end.bytesize
|
184
|
-
|
185
|
-
if line.bytesize > maxlength
|
186
|
-
splitted = []
|
187
|
-
|
188
|
-
while line.bytesize > maxlength_without_end
|
189
|
-
pos = line.rindex(/\s/, maxlength_without_end)
|
190
|
-
r = pos || maxlength_without_end
|
191
|
-
splitted << line.slice!(0, r) + split_end.tr(" ", "\u00A0")
|
192
|
-
line = split_start.tr(" ", "\u00A0") + line.lstrip
|
193
|
-
end
|
194
|
-
|
195
|
-
splitted << line
|
196
|
-
splitted[0, (@config.max_messages || splitted.size)].each do |string|
|
197
|
-
string.tr!("\u00A0", " ") # clean string from any non-breaking spaces
|
198
|
-
raw("#{command} #{recipient} :#{string}")
|
199
|
-
end
|
200
|
-
else
|
201
|
-
raw("#{command} #{recipient} :#{line}")
|
202
|
-
end
|
203
|
-
end
|
204
|
-
end
|
205
|
-
alias_method :privmsg, :msg
|
206
|
-
alias_method :send, :msg
|
207
|
-
|
208
|
-
# Sends a NOTICE to a recipient (a channel or user).
|
209
|
-
# You should be using {Channel#notice} and {User#notice} instead.
|
210
|
-
#
|
211
|
-
# @param [String] recipient the recipient
|
212
|
-
# @param [String] text the message to send
|
213
|
-
# @return [void]
|
214
|
-
# @see Channel#notice
|
215
|
-
# @see User#notice
|
216
|
-
# @see #safe_notice
|
217
|
-
def notice(recipient, text)
|
218
|
-
msg(recipient, text, true)
|
219
|
-
end
|
220
|
-
|
221
|
-
# Like {#msg}, but remove any non-printable characters from
|
222
|
-
# `text`. The purpose of this method is to send text of untrusted
|
223
|
-
# sources, like other users or feeds.
|
224
|
-
#
|
225
|
-
# Note: this will **break** any mIRC color codes embedded in the
|
226
|
-
# string.
|
227
|
-
#
|
228
|
-
# @return (see #msg)
|
229
|
-
# @param (see #msg)
|
230
|
-
# @see #msg
|
231
|
-
# @see User#safe_send
|
232
|
-
# @see Channel#safe_send
|
233
|
-
# @todo Handle mIRC color codes more gracefully.
|
234
|
-
def safe_msg(recipient, text)
|
235
|
-
msg(recipient, Cinch.filter_string(text))
|
236
|
-
end
|
237
|
-
alias_method :safe_privmsg, :safe_msg
|
238
|
-
alias_method :safe_send, :safe_msg
|
239
|
-
|
240
|
-
# Like {#safe_msg} but for notices.
|
241
|
-
#
|
242
|
-
# @return (see #safe_msg)
|
243
|
-
# @param (see #safe_msg)
|
244
|
-
# @see #safe_notice
|
245
|
-
# @see #notice
|
246
|
-
# @see User#safe_notice
|
247
|
-
# @see Channel#safe_notice
|
248
|
-
# @todo (see #safe_msg)
|
249
|
-
def safe_notice(recipient, text)
|
250
|
-
msg(recipient, Cinch.filter_string(text), true)
|
251
|
-
end
|
252
|
-
|
253
|
-
# Invoke an action (/me) in/to a recipient (a channel or user).
|
254
|
-
# You should be using {Channel#action} and {User#action} instead.
|
255
|
-
#
|
256
|
-
# @param [String] recipient the recipient
|
257
|
-
# @param [String] text the message to send
|
258
|
-
# @return [void]
|
259
|
-
# @see Channel#action
|
260
|
-
# @see User#action
|
261
|
-
# @see #safe_action
|
262
|
-
def action(recipient, text)
|
263
|
-
raw("PRIVMSG #{recipient} :\001ACTION #{text}\001")
|
264
|
-
end
|
265
|
-
|
266
|
-
# Like {#action}, but remove any non-printable characters from
|
267
|
-
# `text`. The purpose of this method is to send text from
|
268
|
-
# untrusted sources, like other users or feeds.
|
269
|
-
#
|
270
|
-
# Note: this will **break** any mIRC color codes embedded in the
|
271
|
-
# string.
|
272
|
-
#
|
273
|
-
# @param (see #action)
|
274
|
-
# @return (see #action)
|
275
|
-
# @see #action
|
276
|
-
# @see Channel#safe_action
|
277
|
-
# @see User#safe_action
|
278
|
-
# @todo Handle mIRC color codes more gracefully.
|
279
|
-
def safe_action(recipient, text)
|
280
|
-
action(recipient, Cinch.filter_string(text))
|
281
|
-
end
|
282
|
-
|
283
|
-
# @endgroup
|
284
172
|
# @group Events & Plugins
|
285
173
|
|
286
174
|
# Registers a handler.
|
287
175
|
#
|
288
|
-
# @param [String, Symbol, Integer] event the event to match.
|
289
|
-
#
|
290
|
-
#
|
176
|
+
# @param [String, Symbol, Integer] event the event to match. For a
|
177
|
+
# list of available events, check the {file:events.md Events
|
178
|
+
# documentation}.
|
291
179
|
#
|
292
|
-
#
|
293
|
-
# - :private (a private message)
|
294
|
-
# - :message (both channel and private messages)
|
295
|
-
# - :error (handling errors, use a numeric error code as `match`)
|
296
|
-
# - :ctcp (ctcp requests, use a ctcp command as `match`)
|
297
|
-
#
|
298
|
-
# @param [Regexp, String, Integer] match every message of the
|
180
|
+
# @param [Regexp, Pattern, String] regexp every message of the
|
299
181
|
# right event will be checked against this argument and the event
|
300
182
|
# will only be called if it matches
|
301
183
|
#
|
184
|
+
# @param [Array<Object>] *args Arguments that should be passed to
|
185
|
+
# the block, additionally to capture groups of the regexp.
|
186
|
+
#
|
302
187
|
# @yieldparam [String] *args each capture group of the regex will
|
303
|
-
# be one argument to the block.
|
304
|
-
# though
|
188
|
+
# be one argument to the block.
|
305
189
|
#
|
306
|
-
# @return [
|
307
|
-
def on(event,
|
308
|
-
regexps = [*regexps]
|
309
|
-
regexps = [//] if regexps.empty?
|
310
|
-
|
190
|
+
# @return [Handler] The handlers that have been registered
|
191
|
+
def on(event, regexp = //, *args, &block)
|
311
192
|
event = event.to_sym
|
312
193
|
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
end
|
326
|
-
debug "[on handler] Registering handler with pattern `#{pattern.inspect}`, reacting on `#{event}`"
|
327
|
-
pattern
|
328
|
-
end
|
329
|
-
(@events[event] ||= []) << [regexps, args, block]
|
330
|
-
end
|
194
|
+
pattern = case regexp
|
195
|
+
when Pattern
|
196
|
+
regexp
|
197
|
+
when Regexp
|
198
|
+
Pattern.new(nil, regexp, nil)
|
199
|
+
else
|
200
|
+
if event == :ctcp
|
201
|
+
Pattern.generate(:ctcp, regexp)
|
202
|
+
else
|
203
|
+
Pattern.new(/^/, /#{Regexp.escape(regexp.to_s)}/, /$/)
|
204
|
+
end
|
205
|
+
end
|
331
206
|
|
332
|
-
|
333
|
-
|
334
|
-
# and attached to the event, or nil.
|
335
|
-
# @param [Array] *arguments A list of additional arguments to pass
|
336
|
-
# to event handlers
|
337
|
-
# @return [void]
|
338
|
-
def dispatch(event, msg = nil, *arguments)
|
339
|
-
if handlers = find(event, msg)
|
340
|
-
handlers.each do |handler|
|
341
|
-
regexps, args, block = *handler
|
342
|
-
# calling Message#match multiple times is not a problem
|
343
|
-
# because we cache the result
|
344
|
-
if msg
|
345
|
-
regexp = regexps.find { |rx|
|
346
|
-
msg.match(rx.to_r(msg), event)
|
347
|
-
}
|
348
|
-
captures = msg.match(regexp.to_r(msg), event).captures
|
349
|
-
else
|
350
|
-
captures = []
|
351
|
-
end
|
207
|
+
handler = Handler.new(self, event, pattern, {args: args, execute_in_callback: true}, &block)
|
208
|
+
@handlers.register(handler)
|
352
209
|
|
353
|
-
|
354
|
-
end
|
355
|
-
end
|
356
|
-
end
|
357
|
-
|
358
|
-
# Register all plugins from `@config.plugins.plugins`.
|
359
|
-
#
|
360
|
-
# @return [void]
|
361
|
-
def register_plugins
|
362
|
-
@config.plugins.plugins.each do |plugin|
|
363
|
-
register_plugin(plugin)
|
364
|
-
end
|
365
|
-
end
|
366
|
-
|
367
|
-
# Registers a plugin.
|
368
|
-
#
|
369
|
-
# @param [Class<Plugin>] plugin The plugin class to register
|
370
|
-
# @return [void]
|
371
|
-
def register_plugin(plugin)
|
372
|
-
@plugins << plugin.new(self)
|
210
|
+
return handler
|
373
211
|
end
|
374
212
|
|
375
213
|
# @endgroup
|
@@ -380,8 +218,8 @@ module Cinch
|
|
380
218
|
#
|
381
219
|
# @yieldparam [Struct] config the bot's config
|
382
220
|
# @return [void]
|
383
|
-
def configure
|
384
|
-
@
|
221
|
+
def configure
|
222
|
+
yield @config
|
385
223
|
end
|
386
224
|
|
387
225
|
# Disconnects from the server.
|
@@ -390,15 +228,11 @@ module Cinch
|
|
390
228
|
# @return [void]
|
391
229
|
def quit(message = nil)
|
392
230
|
@quitting = true
|
393
|
-
command
|
394
|
-
|
231
|
+
command = message ? "QUIT :#{message}" : "QUIT"
|
232
|
+
|
233
|
+
@irc.send command
|
395
234
|
end
|
396
235
|
|
397
|
-
# Connects the bot to a server.
|
398
|
-
#
|
399
|
-
# @param [Boolean] plugins Automatically register plugins from
|
400
|
-
# `@config.plugins.plugins`?
|
401
|
-
# @return [void]
|
402
236
|
# Connects the bot to a server.
|
403
237
|
#
|
404
238
|
# @param [Boolean] plugins Automatically register plugins from
|
@@ -406,21 +240,37 @@ module Cinch
|
|
406
240
|
# @return [void]
|
407
241
|
def start(plugins = true)
|
408
242
|
@reconnects = 0
|
409
|
-
register_plugins if plugins
|
243
|
+
@plugins.register_plugins(@config.plugins.plugins) if plugins
|
410
244
|
|
411
245
|
begin
|
412
|
-
@
|
246
|
+
@user_list.each do |user|
|
413
247
|
user.in_whois = false
|
414
248
|
user.unsync_all
|
415
249
|
end # reset state of all users
|
416
250
|
|
417
|
-
@
|
251
|
+
@channel_list.each do |channel|
|
418
252
|
channel.unsync_all
|
419
253
|
end # reset state of all channels
|
420
254
|
|
421
|
-
@
|
255
|
+
@join_handler.unregister if @join_handler
|
256
|
+
@join_timer.stop if @join_timer
|
257
|
+
|
258
|
+
join_lambda = lambda { @config.channels.each { |channel| Channel(channel).join }}
|
259
|
+
|
260
|
+
if @config.delay_joins.is_a?(Symbol)
|
261
|
+
@join_handler = join_handler = on(@config.delay_joins) {
|
262
|
+
join_handler.unregister
|
263
|
+
join_lambda.call
|
264
|
+
}.first
|
265
|
+
else
|
266
|
+
@join_timer = Timer.new(self, interval: @config.delay_joins, shots: 1) {
|
267
|
+
join_lambda.call
|
268
|
+
}
|
269
|
+
end
|
270
|
+
|
271
|
+
@loggers.info "Connecting to #{@config.server}:#{@config.port}"
|
422
272
|
@irc = IRC.new(self)
|
423
|
-
@irc.
|
273
|
+
@irc.start
|
424
274
|
|
425
275
|
if @config.reconnect && !@quitting
|
426
276
|
# double the delay for each unsuccesful reconnection attempt
|
@@ -431,15 +281,26 @@ module Cinch
|
|
431
281
|
@reconnects += 1
|
432
282
|
end
|
433
283
|
|
434
|
-
#
|
435
|
-
# throttled by the IRC server
|
284
|
+
# Throttle reconnect attempts
|
436
285
|
wait = 2**@reconnects
|
437
|
-
@
|
438
|
-
|
286
|
+
wait = @config.max_reconnect_delay if wait > @config.max_reconnect_delay
|
287
|
+
@loggers.info "Waiting #{wait} seconds before reconnecting"
|
288
|
+
start_time = Time.now
|
289
|
+
while !@quitting && (Time.now - start_time) < wait
|
290
|
+
sleep 1
|
291
|
+
end
|
439
292
|
end
|
440
293
|
end while @config.reconnect and not @quitting
|
441
294
|
end
|
442
295
|
|
296
|
+
# TODO document this
|
297
|
+
def stop
|
298
|
+
@plugins.each do |plugin|
|
299
|
+
plugin.storage.save
|
300
|
+
plugin.storage.unload
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
443
304
|
# @endgroup
|
444
305
|
# @group Channel Control
|
445
306
|
|
@@ -447,30 +308,30 @@ module Cinch
|
|
447
308
|
#
|
448
309
|
# @param [String, Channel] channel either the name of a channel or a {Channel} object
|
449
310
|
# @param [String] key optionally the key of the channel
|
450
|
-
# @return [
|
311
|
+
# @return [Channel] The joined channel
|
451
312
|
# @see Channel#join
|
452
313
|
def join(channel, key = nil)
|
453
|
-
Channel(channel)
|
314
|
+
channel = Channel(channel)
|
315
|
+
channel.join(key)
|
316
|
+
|
317
|
+
channel
|
454
318
|
end
|
455
319
|
|
456
320
|
# Part a channel.
|
457
321
|
#
|
458
322
|
# @param [String, Channel] channel either the name of a channel or a {Channel} object
|
459
323
|
# @param [String] reason an optional reason/part message
|
460
|
-
# @return [
|
324
|
+
# @return [Channel] The channel that was left
|
461
325
|
# @see Channel#part
|
462
326
|
def part(channel, reason = nil)
|
463
|
-
Channel(channel)
|
327
|
+
channel = Channel(channel)
|
328
|
+
channel.part(reason)
|
329
|
+
|
330
|
+
channel
|
464
331
|
end
|
465
332
|
|
466
333
|
# @endgroup
|
467
334
|
|
468
|
-
# (see Logger::Logger#debug)
|
469
|
-
# @see Logger::Logger#debug
|
470
|
-
def debug(msg)
|
471
|
-
@logger.debug(msg)
|
472
|
-
end
|
473
|
-
|
474
335
|
# @return [Boolean] True if the bot reports ISUPPORT violations as
|
475
336
|
# exceptions.
|
476
337
|
def strict?
|
@@ -479,66 +340,76 @@ module Cinch
|
|
479
340
|
|
480
341
|
# @yield
|
481
342
|
def initialize(&b)
|
482
|
-
@
|
483
|
-
@
|
484
|
-
@config = OpenStruct.new({
|
485
|
-
:server => "localhost",
|
486
|
-
:port => 6667,
|
487
|
-
:ssl => OpenStruct.new({
|
488
|
-
:use => false,
|
489
|
-
:verify => false,
|
490
|
-
:client_cert => nil,
|
491
|
-
:ca_path => "/etc/ssl/certs",
|
492
|
-
}),
|
493
|
-
:password => nil,
|
494
|
-
:nick => "cinch",
|
495
|
-
:nicks => nil,
|
496
|
-
:realname => "cinch",
|
497
|
-
:user => "cinch",
|
498
|
-
:verbose => true,
|
499
|
-
:messages_per_second => 0.5,
|
500
|
-
:server_queue_size => 10,
|
501
|
-
:strictness => :forgiving,
|
502
|
-
:message_split_start => '... ',
|
503
|
-
:message_split_end => ' ...',
|
504
|
-
:max_messages => nil,
|
505
|
-
:plugins => OpenStruct.new({
|
506
|
-
:plugins => [],
|
507
|
-
:prefix => /^!/,
|
508
|
-
:suffix => nil,
|
509
|
-
:options => Hash.new {|h,k| h[k] = {}},
|
510
|
-
}),
|
511
|
-
:channels => [],
|
512
|
-
:encoding => :irc,
|
513
|
-
:reconnect => true,
|
514
|
-
:local_host => nil,
|
515
|
-
:timeouts => OpenStruct.new({
|
516
|
-
:read => 240,
|
517
|
-
:connect => 10,
|
518
|
-
}),
|
519
|
-
:ping_interval => 120,
|
520
|
-
})
|
343
|
+
@loggers = LoggerList.new
|
344
|
+
@loggers << Logger::FormattedLogger.new($stderr)
|
521
345
|
|
346
|
+
@config = Configuration::Bot.new
|
347
|
+
@handlers = HandlerList.new
|
522
348
|
@semaphores_mutex = Mutex.new
|
523
|
-
@semaphores
|
524
|
-
@
|
525
|
-
@
|
526
|
-
@
|
527
|
-
@
|
528
|
-
@quitting = false
|
349
|
+
@semaphores = Hash.new { |h, k| h[k] = Mutex.new }
|
350
|
+
@callback = Callback.new(self)
|
351
|
+
@channels = []
|
352
|
+
@quitting = false
|
353
|
+
@modes = []
|
529
354
|
|
530
|
-
@
|
531
|
-
@
|
355
|
+
@user_list = UserList.new(self)
|
356
|
+
@channel_list = ChannelList.new(self)
|
357
|
+
@plugins = PluginList.new(self)
|
532
358
|
|
359
|
+
super(nil, self)
|
533
360
|
instance_eval(&b) if block_given?
|
361
|
+
end
|
534
362
|
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
363
|
+
# @since 2.0.0
|
364
|
+
# @return [self]
|
365
|
+
# @api private
|
366
|
+
def bot
|
367
|
+
# This method is needed for the Helpers interface
|
368
|
+
self
|
369
|
+
end
|
370
|
+
|
371
|
+
# Sets a mode on the bot.
|
372
|
+
#
|
373
|
+
# @param [String] mode
|
374
|
+
# @return [void]
|
375
|
+
# @since 2.0.0
|
376
|
+
# @see Bot#modes
|
377
|
+
# @see Bot#unset_mode
|
378
|
+
def set_mode(mode)
|
379
|
+
@modes << mode unless @modes.include?(mode)
|
380
|
+
@irc.send "MODE #{nick} +#{mode}"
|
381
|
+
end
|
382
|
+
|
383
|
+
# Unsets a mode on the bot.
|
384
|
+
#
|
385
|
+
# @param [String] mode
|
386
|
+
# @return [void]
|
387
|
+
# @since 2.0.0
|
388
|
+
def unset_mode(mode)
|
389
|
+
@modes.delete(mode)
|
390
|
+
@irc.send "MODE #{nick} -#{mode}"
|
391
|
+
end
|
392
|
+
|
393
|
+
# @since 2.0.0
|
394
|
+
def modes=(modes)
|
395
|
+
(@modes - modes).each do |mode|
|
396
|
+
unset_mode(mode)
|
397
|
+
end
|
398
|
+
|
399
|
+
(modes - @modes).each do |mode|
|
400
|
+
set_mode(mode)
|
539
401
|
end
|
540
402
|
end
|
541
403
|
|
404
|
+
# Used for updating the bot's nick from within the IRC parser.
|
405
|
+
#
|
406
|
+
# @param [String] nick
|
407
|
+
# @api private
|
408
|
+
# @return [String]
|
409
|
+
def set_nick(nick)
|
410
|
+
@name = nick
|
411
|
+
end
|
412
|
+
|
542
413
|
# The bot's nickname.
|
543
414
|
# @overload nick=(new_nick)
|
544
415
|
# @raise [Exceptions::NickTooLong] Raised if the bot is
|
@@ -548,11 +419,8 @@ module Cinch
|
|
548
419
|
# @overload nick
|
549
420
|
# @return [String]
|
550
421
|
# @return [String]
|
551
|
-
attr_accessor :nick
|
552
|
-
undef_method "nick"
|
553
|
-
undef_method "nick="
|
554
422
|
def nick
|
555
|
-
@
|
423
|
+
@name
|
556
424
|
end
|
557
425
|
|
558
426
|
def nick=(new_nick)
|
@@ -560,7 +428,7 @@ module Cinch
|
|
560
428
|
raise Exceptions::NickTooLong, new_nick
|
561
429
|
end
|
562
430
|
@config.nick = new_nick
|
563
|
-
|
431
|
+
@irc.send "NICK #{new_nick}"
|
564
432
|
end
|
565
433
|
|
566
434
|
# Try to create a free nick, first by cycling through all
|
@@ -568,80 +436,37 @@ module Cinch
|
|
568
436
|
#
|
569
437
|
# @param [String] base The base nick to start trying from
|
570
438
|
# @api private
|
571
|
-
# @return String
|
572
|
-
|
439
|
+
# @return [String]
|
440
|
+
# @since 2.0.0
|
441
|
+
def generate_next_nick!(base = nil)
|
573
442
|
nicks = @config.nicks || []
|
574
443
|
|
575
444
|
if base
|
576
445
|
# if `base` is not in our list of nicks to try, assume that it's
|
577
446
|
# custom and just append an underscore
|
578
447
|
if !nicks.include?(base)
|
579
|
-
|
448
|
+
new_nick = base + "_"
|
580
449
|
else
|
581
450
|
# if we have a base, try the next nick or append an
|
582
451
|
# underscore if no more nicks are left
|
583
452
|
new_index = nicks.index(base) + 1
|
584
453
|
if nicks[new_index]
|
585
|
-
|
454
|
+
new_nick = nicks[new_index]
|
586
455
|
else
|
587
|
-
|
456
|
+
new_nick = base + "_"
|
588
457
|
end
|
589
458
|
end
|
590
459
|
else
|
591
460
|
# if we have no base, try the first possible nick
|
592
461
|
new_nick = @config.nicks ? @config.nicks.first : @config.nick
|
593
462
|
end
|
594
|
-
end
|
595
|
-
|
596
|
-
# @return [Boolean] True if the bot is using SSL to connect to the
|
597
|
-
# server.
|
598
|
-
def secure?
|
599
|
-
@config[:ssl] == true || (@config[:ssl].is_a?(Hash) && @config[:ssl][:use])
|
600
|
-
end
|
601
|
-
|
602
|
-
# This method is only provided in order to give Bot and User a
|
603
|
-
# common interface.
|
604
|
-
#
|
605
|
-
# @return [false] Always returns `false`.
|
606
|
-
# @see User#unknown? See User#unknown? for the method's real use.
|
607
|
-
def unknown?
|
608
|
-
false
|
609
|
-
end
|
610
|
-
|
611
|
-
[:host, :mask, :user, :realname, :signed_on_at, :secure?].each do |attr|
|
612
|
-
define_method(attr) do
|
613
|
-
User(nick).__send__(attr)
|
614
|
-
end
|
615
|
-
end
|
616
463
|
|
617
|
-
|
618
|
-
def find(type, msg = nil)
|
619
|
-
if events = @events[type]
|
620
|
-
if msg.nil?
|
621
|
-
return events
|
622
|
-
end
|
623
|
-
|
624
|
-
events.select { |regexps|
|
625
|
-
regexps.first.any? { |regexp|
|
626
|
-
msg.match(regexp.to_r(msg), type)
|
627
|
-
}
|
628
|
-
}
|
629
|
-
end
|
464
|
+
@config.nick = new_nick
|
630
465
|
end
|
631
466
|
|
632
|
-
|
633
|
-
|
634
|
-
@
|
635
|
-
begin
|
636
|
-
catch(:halt) do
|
637
|
-
@callback.instance_exec(msg, *args, *bargs, &block)
|
638
|
-
end
|
639
|
-
rescue => e
|
640
|
-
@logger.log_exception(e)
|
641
|
-
ensure
|
642
|
-
@handler_threads.delete Thread.current
|
643
|
-
end
|
644
|
-
end
|
467
|
+
# @return [String]
|
468
|
+
def inspect
|
469
|
+
"#<Bot nick=#{@name.inspect}>"
|
645
470
|
end
|
646
471
|
end
|
647
472
|
end
|