butler 1.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +4 -0
- data/GPL.txt +340 -0
- data/LICENSE.txt +52 -0
- data/README +37 -0
- data/Rakefile +334 -0
- data/bin/botcontrol +230 -0
- data/data/butler/config_template.yaml +4 -0
- data/data/butler/dialogs/backup.rb +19 -0
- data/data/butler/dialogs/botcontrol.rb +4 -0
- data/data/butler/dialogs/config.rb +1 -0
- data/data/butler/dialogs/create.rb +53 -0
- data/data/butler/dialogs/delete.rb +3 -0
- data/data/butler/dialogs/en/backup.yaml +6 -0
- data/data/butler/dialogs/en/botcontrol.yaml +5 -0
- data/data/butler/dialogs/en/create.yaml +11 -0
- data/data/butler/dialogs/en/delete.yaml +2 -0
- data/data/butler/dialogs/en/help.yaml +17 -0
- data/data/butler/dialogs/en/info.yaml +13 -0
- data/data/butler/dialogs/en/list.yaml +4 -0
- data/data/butler/dialogs/en/notyetimplemented.yaml +2 -0
- data/data/butler/dialogs/en/rename.yaml +3 -0
- data/data/butler/dialogs/en/start.yaml +3 -0
- data/data/butler/dialogs/en/sync_plugins.yaml +3 -0
- data/data/butler/dialogs/en/uninstall.yaml +5 -0
- data/data/butler/dialogs/en/unknown_command.yaml +2 -0
- data/data/butler/dialogs/help.rb +11 -0
- data/data/butler/dialogs/info.rb +27 -0
- data/data/butler/dialogs/interactive.rb +1 -0
- data/data/butler/dialogs/list.rb +10 -0
- data/data/butler/dialogs/notyetimplemented.rb +1 -0
- data/data/butler/dialogs/rename.rb +4 -0
- data/data/butler/dialogs/selectbot.rb +2 -0
- data/data/butler/dialogs/start.rb +5 -0
- data/data/butler/dialogs/sync_plugins.rb +30 -0
- data/data/butler/dialogs/uninstall.rb +17 -0
- data/data/butler/dialogs/unknown_command.rb +1 -0
- data/data/butler/plugins/core/logout.rb +41 -0
- data/data/butler/plugins/core/plugins.rb +134 -0
- data/data/butler/plugins/core/privilege.rb +103 -0
- data/data/butler/plugins/core/user.rb +166 -0
- data/data/butler/plugins/dev/eval.rb +64 -0
- data/data/butler/plugins/dev/nometa.rb +14 -0
- data/data/butler/plugins/dev/onhandlers.rb +93 -0
- data/data/butler/plugins/dev/raw.rb +36 -0
- data/data/butler/plugins/dev/rawlog.rb +77 -0
- data/data/butler/plugins/games/eightball.rb +54 -0
- data/data/butler/plugins/games/mastermind.rb +174 -0
- data/data/butler/plugins/irc/action.rb +36 -0
- data/data/butler/plugins/irc/join.rb +38 -0
- data/data/butler/plugins/irc/notice.rb +36 -0
- data/data/butler/plugins/irc/part.rb +38 -0
- data/data/butler/plugins/irc/privmsg.rb +36 -0
- data/data/butler/plugins/irc/quit.rb +36 -0
- data/data/butler/plugins/operator/deop.rb +41 -0
- data/data/butler/plugins/operator/devoice.rb +41 -0
- data/data/butler/plugins/operator/limit.rb +47 -0
- data/data/butler/plugins/operator/op.rb +41 -0
- data/data/butler/plugins/operator/voice.rb +41 -0
- data/data/butler/plugins/public/help.rb +69 -0
- data/data/butler/plugins/public/login.rb +72 -0
- data/data/butler/plugins/public/usage.rb +49 -0
- data/data/butler/plugins/service/clones.rb +56 -0
- data/data/butler/plugins/service/define.rb +47 -0
- data/data/butler/plugins/service/log.rb +183 -0
- data/data/butler/plugins/service/svn.rb +91 -0
- data/data/butler/plugins/util/cycle.rb +98 -0
- data/data/butler/plugins/util/load.rb +41 -0
- data/data/butler/plugins/util/pong.rb +29 -0
- data/data/butler/strings/random/acknowledge.en.yaml +5 -0
- data/data/butler/strings/random/gratitude.en.yaml +3 -0
- data/data/butler/strings/random/hello.en.yaml +4 -0
- data/data/butler/strings/random/ignorance.en.yaml +7 -0
- data/data/butler/strings/random/ignorance_about.en.yaml +3 -0
- data/data/butler/strings/random/insult.en.yaml +3 -0
- data/data/butler/strings/random/rejection.en.yaml +12 -0
- data/data/man/botcontrol.1 +17 -0
- data/lib/access.rb +187 -0
- data/lib/access/admin.rb +16 -0
- data/lib/access/privilege.rb +122 -0
- data/lib/access/role.rb +102 -0
- data/lib/access/savable.rb +18 -0
- data/lib/access/user.rb +180 -0
- data/lib/access/yamlbase.rb +126 -0
- data/lib/butler.rb +188 -0
- data/lib/butler/bot.rb +247 -0
- data/lib/butler/control.rb +93 -0
- data/lib/butler/dialog.rb +64 -0
- data/lib/butler/initialvalues.rb +40 -0
- data/lib/butler/irc/channel.rb +135 -0
- data/lib/butler/irc/channels.rb +96 -0
- data/lib/butler/irc/client.rb +351 -0
- data/lib/butler/irc/hostmask.rb +53 -0
- data/lib/butler/irc/message.rb +184 -0
- data/lib/butler/irc/parser.rb +125 -0
- data/lib/butler/irc/parser/commands.rb +83 -0
- data/lib/butler/irc/parser/generic.rb +343 -0
- data/lib/butler/irc/socket.rb +378 -0
- data/lib/butler/irc/string.rb +186 -0
- data/lib/butler/irc/topic.rb +15 -0
- data/lib/butler/irc/user.rb +265 -0
- data/lib/butler/irc/users.rb +112 -0
- data/lib/butler/plugin.rb +249 -0
- data/lib/butler/plugin/configproxy.rb +35 -0
- data/lib/butler/plugin/mapper.rb +85 -0
- data/lib/butler/plugin/matcher.rb +55 -0
- data/lib/butler/plugin/onhandlers.rb +70 -0
- data/lib/butler/plugin/trigger.rb +58 -0
- data/lib/butler/plugins.rb +147 -0
- data/lib/butler/version.rb +17 -0
- data/lib/cloptions.rb +217 -0
- data/lib/cloptions/adapters.rb +24 -0
- data/lib/cloptions/switch.rb +132 -0
- data/lib/configuration.rb +223 -0
- data/lib/dialogline.rb +296 -0
- data/lib/dialogline/localizations.rb +24 -0
- data/lib/durations.rb +57 -0
- data/lib/event.rb +295 -0
- data/lib/event/at.rb +64 -0
- data/lib/event/every.rb +56 -0
- data/lib/event/timed.rb +112 -0
- data/lib/installer.rb +75 -0
- data/lib/iterator.rb +34 -0
- data/lib/log.rb +68 -0
- data/lib/log/comfort.rb +85 -0
- data/lib/log/converter.rb +23 -0
- data/lib/log/entry.rb +152 -0
- data/lib/log/fakeio.rb +55 -0
- data/lib/log/file.rb +54 -0
- data/lib/log/filereader.rb +81 -0
- data/lib/log/forward.rb +49 -0
- data/lib/log/methods.rb +39 -0
- data/lib/log/nolog.rb +18 -0
- data/lib/log/splitter.rb +26 -0
- data/lib/ostructfixed.rb +26 -0
- data/lib/ruby/array/columnize.rb +38 -0
- data/lib/ruby/dir/mktree.rb +28 -0
- data/lib/ruby/enumerable/join.rb +13 -0
- data/lib/ruby/exception/detailed.rb +24 -0
- data/lib/ruby/file/append.rb +11 -0
- data/lib/ruby/file/write.rb +11 -0
- data/lib/ruby/hash/zip.rb +15 -0
- data/lib/ruby/kernel/bench.rb +15 -0
- data/lib/ruby/kernel/daemonize.rb +42 -0
- data/lib/ruby/kernel/non_verbose.rb +17 -0
- data/lib/ruby/kernel/safe_fork.rb +18 -0
- data/lib/ruby/range/stepped.rb +11 -0
- data/lib/ruby/string/arguments.rb +72 -0
- data/lib/ruby/string/chunks.rb +15 -0
- data/lib/ruby/string/post_arguments.rb +44 -0
- data/lib/ruby/string/unescaped.rb +17 -0
- data/lib/scheduler.rb +164 -0
- data/lib/scriptfile.rb +101 -0
- data/lib/templater.rb +86 -0
- data/test/cloptions.rb +134 -0
- data/test/cv.rb +28 -0
- data/test/irc/client.rb +85 -0
- data/test/irc/client_login.txt +53 -0
- data/test/irc/client_subscribe.txt +8 -0
- data/test/irc/message.rb +30 -0
- data/test/irc/messages.txt +64 -0
- data/test/irc/parser.rb +13 -0
- data/test/irc/profile_parser.rb +12 -0
- data/test/irc/users.rb +28 -0
- metadata +256 -0
@@ -0,0 +1,96 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2007 by Stefan Rusterholz.
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE.txt for permissions.
|
5
|
+
#++
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
require 'butler/irc/string'
|
10
|
+
require 'butler/irc/channel'
|
11
|
+
require 'butler/irc/users'
|
12
|
+
require 'thread'
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
class Butler
|
17
|
+
module IRC
|
18
|
+
CHANNEL_SIGNALS = [
|
19
|
+
:joined, # myself joined a channel
|
20
|
+
:parted, # myself parted the channel
|
21
|
+
:kicked, # myself got kicked from the channel
|
22
|
+
:quitted, # myself has quit
|
23
|
+
:left, # same as :parted || :kicked || :quitted
|
24
|
+
:join, # the user joined the channel
|
25
|
+
:part, # the user parted the channel
|
26
|
+
:kick, # the user got kicked from the channel
|
27
|
+
:quit, # the user has quit
|
28
|
+
:leave, # same as :part || :kick || :quit
|
29
|
+
:topic, # topic has changed
|
30
|
+
:mode, # mode has changed
|
31
|
+
]
|
32
|
+
|
33
|
+
# Butler::IRC::Channel represents a channel in IRC.
|
34
|
+
class Channels
|
35
|
+
include Enumerable
|
36
|
+
|
37
|
+
def initialize(client)
|
38
|
+
@all = {}
|
39
|
+
@client = client
|
40
|
+
@users = nil
|
41
|
+
@lock = Mutex.new
|
42
|
+
end
|
43
|
+
|
44
|
+
def users=(users)
|
45
|
+
raise "Can't set @users twice" if @users
|
46
|
+
@users = users
|
47
|
+
end
|
48
|
+
|
49
|
+
# Will return the single instance representing the channel
|
50
|
+
# with name 'channel'.
|
51
|
+
def create(channel, *args)
|
52
|
+
@lock.synchronize {
|
53
|
+
channel = self[channel] || Channel.new(@users, channel, *args)
|
54
|
+
@all[channel.to_str] = channel
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
# Iterates through all known channels (@all)
|
59
|
+
def each(&block)
|
60
|
+
@all.each_value(&block)
|
61
|
+
end
|
62
|
+
|
63
|
+
# returns channelcount
|
64
|
+
def length
|
65
|
+
@all.length
|
66
|
+
end
|
67
|
+
|
68
|
+
# returns the lowercased channelnames
|
69
|
+
def channel_names
|
70
|
+
@all.keys
|
71
|
+
end
|
72
|
+
|
73
|
+
# returns all channels with that name (names should
|
74
|
+
# be normalized to lowercased name)
|
75
|
+
#
|
76
|
+
def map_names(*names)
|
77
|
+
@all.values_at(*names)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Remove a channel from the channels-list
|
81
|
+
def delete(channel)
|
82
|
+
@all.delete(channel.to_str.downcase)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Remove user from all channels
|
86
|
+
def delete_user(user, reason) #:nodoc:
|
87
|
+
@all.each_value { |channel| channel.delete_user(user, reason) }
|
88
|
+
end
|
89
|
+
|
90
|
+
# Get the channel with name 'channel'
|
91
|
+
def [](channel)
|
92
|
+
@all[channel.to_str.downcase]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,351 @@
|
|
1
|
+
require 'butler/irc/channels'
|
2
|
+
require 'butler/irc/parser'
|
3
|
+
require 'butler/irc/socket'
|
4
|
+
require 'butler/irc/users'
|
5
|
+
require 'log/comfort'
|
6
|
+
require 'timeout'
|
7
|
+
require 'thread'
|
8
|
+
|
9
|
+
class Butler
|
10
|
+
module IRC
|
11
|
+
# Reply for Butler::IRC::Client#whois
|
12
|
+
Whois = Struct.new(:exists, :nick, :user, :host, :real, :registered, :channels, :server, :idle, :signon)
|
13
|
+
|
14
|
+
# == Description
|
15
|
+
# Wraps Butler::IRC::Socket, providing methods to be aware of the environment.
|
16
|
+
# It parses messages, keeps track of users and channels, is aware of events,
|
17
|
+
# responds to pings, provides simple methods for requests (e.g. whois,
|
18
|
+
# banlist, ...) and offers dispatchers for messages and scheduled events.
|
19
|
+
# Butler::IRC::Client uses a separated thread for reading and dispatching.
|
20
|
+
#
|
21
|
+
# == Synopsis
|
22
|
+
# irc_client = Butler::IRC::Client.new('irc.server.com', :port => 6667, :server_charset => 'utf-8')
|
23
|
+
# irc_client.subscribe(:QUIT) { |listener, message| "#{message.from.nick} has left us..." }
|
24
|
+
# puts "Whois 'nickname':", irc_client.whois("nickname")
|
25
|
+
# puts *irc_client.banlist("#channel")
|
26
|
+
# irc_client.event_loop { |message|
|
27
|
+
# case message
|
28
|
+
# when /#{irc_client.myself.nick}[,:]/
|
29
|
+
# message.answer("#{message.from.nick}, you spoke to me?")
|
30
|
+
# when :JOIN
|
31
|
+
# message.from.notice("Welcome to #{message.channel}!")
|
32
|
+
# end
|
33
|
+
# puts "received: #{message}"
|
34
|
+
# }
|
35
|
+
# puts "If this point is reached, client has ended"
|
36
|
+
#
|
37
|
+
class Client
|
38
|
+
class Terminate < RuntimeError; end
|
39
|
+
# Created by Butler::IRC::Client#subscribe() and similar methods
|
40
|
+
Listener = Struct.new(:priority, :callback, :args, :unsubscriber) unless defined? Listener
|
41
|
+
class Listener
|
42
|
+
alias set_priority priority=
|
43
|
+
private :set_priority
|
44
|
+
private :unsubscriber
|
45
|
+
|
46
|
+
def initialize(priority, callback, args=[], &unsubscriber)
|
47
|
+
super(priority, callback, args, unsubscriber)
|
48
|
+
end
|
49
|
+
|
50
|
+
# will remove this listener from the clients dispatcher forever
|
51
|
+
def unsubscribe
|
52
|
+
unsubscriber.call(self)
|
53
|
+
end
|
54
|
+
|
55
|
+
# set the priority of this listener
|
56
|
+
# see Butler::IRC::Client#subscribe() for infos about priority
|
57
|
+
def priority=(value)
|
58
|
+
set_priority(value)
|
59
|
+
container.sort_by { |l| -l.priority }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
module Filter # :nodoc:
|
63
|
+
attr_accessor :listener
|
64
|
+
def unsubscribe
|
65
|
+
listener.unsubscribe
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# The timeout defaults
|
70
|
+
DefaultTimeout = {
|
71
|
+
:login => 150,
|
72
|
+
}
|
73
|
+
|
74
|
+
# Defaults for the opts argument in Butler::Bot.new
|
75
|
+
DefaultOptions = {
|
76
|
+
:client_charset => 'utf-8',
|
77
|
+
:server_charset => 'utf-8',
|
78
|
+
:channel_charset => {},
|
79
|
+
:timeout => {},
|
80
|
+
}
|
81
|
+
|
82
|
+
# The charset the client uses
|
83
|
+
attr_accessor :client_charset
|
84
|
+
|
85
|
+
# The charset the server needs messages sent to him and sends message to
|
86
|
+
# this client in.
|
87
|
+
attr_accessor :server_charset
|
88
|
+
|
89
|
+
# The charsets of individual channel, only set if they differ from
|
90
|
+
# server_charset
|
91
|
+
attr_reader :channel_charset
|
92
|
+
|
93
|
+
attr_reader :channels
|
94
|
+
attr_reader :users
|
95
|
+
|
96
|
+
# The Butler::IRC::Socket, most send commands have to be used on this
|
97
|
+
attr_reader :irc
|
98
|
+
|
99
|
+
# The user representing the bot
|
100
|
+
attr_reader :myself
|
101
|
+
|
102
|
+
def initialize(server, options={}, &on_disconnect)
|
103
|
+
options = DefaultOptions.merge(options)
|
104
|
+
@users = Users.new(self)
|
105
|
+
@channels = Channels.new(self)
|
106
|
+
@users.channels = @channels
|
107
|
+
@channels.users = @users
|
108
|
+
@parser = Parser.new(self, @users, @channels)
|
109
|
+
|
110
|
+
@client_charset = options.delete(:client_charset)
|
111
|
+
@server_charset = options.delete(:server_charset)
|
112
|
+
# proc needed because @server_charset might change
|
113
|
+
@channel_charset = Hash.new { |h,k| @server_charset }.merge(options.delete(:channel_charset))
|
114
|
+
|
115
|
+
@timeout = DefaultTimeout.merge(options.delete(:timeout))
|
116
|
+
|
117
|
+
@irc = Socket.new(server, options) # the socket, all methods to the socket are wrapped
|
118
|
+
|
119
|
+
@listen = Hash.new { |h,k| h[k] = [] }
|
120
|
+
@listener = {}
|
121
|
+
@event_loop = Queue.new
|
122
|
+
@dispatch_lock = Mutex.new
|
123
|
+
@thread_read = nil
|
124
|
+
@myself = @users.myself
|
125
|
+
|
126
|
+
subscribe(:PING, 100) { |listener, message| @irc.pong(message.pong) }
|
127
|
+
end
|
128
|
+
|
129
|
+
# callback is called whenever a message with Message#symbol == symbol
|
130
|
+
# (or on every message if symbol is nil)
|
131
|
+
# priority may be any numeric, higher priority is dispatched to first,
|
132
|
+
# lower priority later
|
133
|
+
# returns an Butler::IRC::Client::Listener
|
134
|
+
def subscribe(symbol=nil, priority=0, id=nil, *args, &callback)
|
135
|
+
id ||= callback
|
136
|
+
raise "#{id} already subscribed" if @listener.has_key?(id)
|
137
|
+
listener = Listener.new(priority, callback, args) { |item|
|
138
|
+
@dispatch_lock.synchronize {
|
139
|
+
@listener.delete(id)
|
140
|
+
@listen[symbol].delete(item)
|
141
|
+
@listen.delete(symbol) if @listen[symbol].empty?
|
142
|
+
}
|
143
|
+
}
|
144
|
+
@dispatch_lock.synchronize {
|
145
|
+
@listener[id] = listener
|
146
|
+
@listen[symbol] << listener
|
147
|
+
@listen[symbol].sort_by { |l| -l.priority }
|
148
|
+
}
|
149
|
+
listener
|
150
|
+
end
|
151
|
+
|
152
|
+
# unsubscribe a listener by id
|
153
|
+
def unsubscribe(id)
|
154
|
+
@dispatch_lock.synchronize {
|
155
|
+
@listener.delete(id)
|
156
|
+
@listen[symbol].delete(item)
|
157
|
+
@listen.delete(symbol) if @listen[symbol].empty?
|
158
|
+
}
|
159
|
+
end
|
160
|
+
|
161
|
+
# blocks current thread until a Message with symbol
|
162
|
+
# (optionally passing a test given as block) is received,
|
163
|
+
# returns the message received that matches.
|
164
|
+
# returns nil if it times out before a match
|
165
|
+
def wait_for(symbol, timeout=nil, &test)
|
166
|
+
timeout(timeout) {
|
167
|
+
queue = Queue.new
|
168
|
+
listener = subscribe(symbol) { |l, m| queue.push(m) }
|
169
|
+
begin
|
170
|
+
message = queue.shift
|
171
|
+
end until block_given? ? yield(message) : true
|
172
|
+
message
|
173
|
+
}
|
174
|
+
rescue Timeout::Error
|
175
|
+
return nil
|
176
|
+
ensure
|
177
|
+
listener.unsubscribe
|
178
|
+
end
|
179
|
+
|
180
|
+
# listens for all Messages with symbol (optionally
|
181
|
+
# passing a test given as block) and pushes them onto the Queue
|
182
|
+
# returns the Queue, extended with Filter. You are responsible to
|
183
|
+
# unsubscribe it (call Queue#unsubscribe on it)
|
184
|
+
def filter(symbol, priority=0, queue=Queue.new)
|
185
|
+
raise ArgumentError, "Invalid Queue #{queue}:#{queue.class}" unless queue.respond_to?(:push)
|
186
|
+
listener = if block_given? then
|
187
|
+
subscribe(symbol, priority) { |l, message| queue.push(message) if yield(message) }
|
188
|
+
else
|
189
|
+
subscribe(symbol, priority) { |l, message| queue.push(message) }
|
190
|
+
end
|
191
|
+
queue.extend Filter
|
192
|
+
queue.listener = listener
|
193
|
+
queue
|
194
|
+
end
|
195
|
+
|
196
|
+
# login under nick, user, real
|
197
|
+
def login(nick, user, real)
|
198
|
+
queue, nick_change = nil, nil
|
199
|
+
number = 0
|
200
|
+
timeout(@timeout[:login]) {
|
201
|
+
@irc.connect
|
202
|
+
@users.create_myself(nick, user, real)
|
203
|
+
@myself = @users.myself
|
204
|
+
queue = filter(:RPL_WELCOME)
|
205
|
+
filter(:ERR_NOMOTD, 0, queue)
|
206
|
+
nick_change = subscribe(:ERR_NICKNAMEINUSE) {
|
207
|
+
change = "[#{number+=1}]#{nick}"
|
208
|
+
@irc.nick(change)
|
209
|
+
@myself.nick = change
|
210
|
+
}
|
211
|
+
@thread_read = Thread.new(&method(:thread_read))
|
212
|
+
@irc.login(nick, user, real)
|
213
|
+
queue.shift
|
214
|
+
}
|
215
|
+
true
|
216
|
+
ensure
|
217
|
+
queue.unsubscribe if queue
|
218
|
+
nick_change.unsubscribe if nick_change
|
219
|
+
end
|
220
|
+
|
221
|
+
# Same as IRC::Socket#join, but will do a who on every joined channel
|
222
|
+
def join(*args)
|
223
|
+
@irc.join(*args).each { |channel|
|
224
|
+
@irc.who(channel)
|
225
|
+
}
|
226
|
+
end
|
227
|
+
|
228
|
+
# Do a whois on nick
|
229
|
+
# Returns an Butler::IRC::Whois-Struct
|
230
|
+
def whois(user)
|
231
|
+
nick = user.to_str.strip_user_prefixes
|
232
|
+
raise ArgumentError, "Invalid nick #{nick.inspect}" unless nick.valid_nickname?
|
233
|
+
queue = Queue.new
|
234
|
+
whois = Whois.new
|
235
|
+
whois.exists = true
|
236
|
+
[
|
237
|
+
:RPL_WHOISUSER,
|
238
|
+
:RPL_WHOISSERVER,
|
239
|
+
:RPL_WHOISIDLE,
|
240
|
+
:RPL_ENDOFWHOIS,
|
241
|
+
:RPL_UNIQOPIS,
|
242
|
+
:RPL_WHOISCHANNELS,
|
243
|
+
:RPL_IDENTIFIED_TO_SERVICES,
|
244
|
+
:ERR_NOSUCHNICK,
|
245
|
+
:RPL_REGISTERED_INFO
|
246
|
+
].each { |reply| filter(reply, 1, queue) }
|
247
|
+
@irc.whois(nick)
|
248
|
+
until (message = queue.shift).symbol == :RPL_ENDOFWHOIS
|
249
|
+
case message.symbol
|
250
|
+
when :ERR_NOSUCHNICK
|
251
|
+
whois.exists = false
|
252
|
+
when :RPL_WHOISUSER
|
253
|
+
whois.exists = true
|
254
|
+
whois.nick = message.nick
|
255
|
+
whois.user = message.user
|
256
|
+
whois.host = message.host
|
257
|
+
whois.real = message.real
|
258
|
+
when :RPL_WHOISSERVER
|
259
|
+
when :RPL_WHOISIDLE
|
260
|
+
whois.exists = true
|
261
|
+
whois.signon = message.signon_time
|
262
|
+
whois.idle = message.seconds_idle
|
263
|
+
when :RPL_UNIQOPIS
|
264
|
+
when :RPL_WHOISCHANNELS
|
265
|
+
whois.exists = true
|
266
|
+
whois.channels = message.channels
|
267
|
+
when :RPL_REGISTERED_INFO
|
268
|
+
whois.exists = true
|
269
|
+
whois.registered = true
|
270
|
+
end
|
271
|
+
end
|
272
|
+
return whois
|
273
|
+
end
|
274
|
+
|
275
|
+
# Sends quit message to server, terminates connection
|
276
|
+
def quit(reason=nil)
|
277
|
+
@irc.quit(reason)
|
278
|
+
terminate
|
279
|
+
@irc.close
|
280
|
+
end
|
281
|
+
|
282
|
+
# terminate all processing and reading,
|
283
|
+
# see Client#quit
|
284
|
+
def terminate(stop_reading=true)
|
285
|
+
@thread_read.raise Terminate if stop_reading and @thread_read and @thread_read.alive?
|
286
|
+
terminate_event_loop
|
287
|
+
end
|
288
|
+
|
289
|
+
# this thread is responsible for reading the servers messages
|
290
|
+
# and dispatching them to responders
|
291
|
+
# normally this thread is alive as long as the client is connected to the server
|
292
|
+
def thread_read
|
293
|
+
while message = @irc.read; process(message); end
|
294
|
+
on_disconnect(:disconnect)
|
295
|
+
rescue Terminate => e # got the termination signal
|
296
|
+
info("Terminating read thread")
|
297
|
+
on_disconnect(:quit)
|
298
|
+
rescue Errno::EPIPE => error # irc server closed connection
|
299
|
+
exception(error)
|
300
|
+
on_disconnect(:disconnect)
|
301
|
+
rescue Exception => error
|
302
|
+
exception(error)
|
303
|
+
on_disconnect(:error)
|
304
|
+
ensure
|
305
|
+
@irc.close
|
306
|
+
end
|
307
|
+
|
308
|
+
def on_disconnect(reason)
|
309
|
+
end
|
310
|
+
|
311
|
+
def event_loop(priority=-1)
|
312
|
+
if block_given?
|
313
|
+
filter(nil, priority, @event_loop)
|
314
|
+
while message = @event_loop.shift; yield(message); end
|
315
|
+
else
|
316
|
+
sleep
|
317
|
+
end
|
318
|
+
ensure
|
319
|
+
@event_loop.unsubscribe if block_given?
|
320
|
+
end
|
321
|
+
|
322
|
+
def terminate_event_loop
|
323
|
+
@event_loop.push(nil) if @event_loop
|
324
|
+
end
|
325
|
+
|
326
|
+
# process a Butler::IRC::Message, normally fed from thread_read.
|
327
|
+
def process(message)
|
328
|
+
message = @parser.server_message(message)
|
329
|
+
message.transcode!(@channel_charset[message.channel], @client_charset)
|
330
|
+
@dispatch_lock.synchronize {
|
331
|
+
@listen[nil].each { |listener| listener.callback.call(listener, message) }
|
332
|
+
if @listen.has_key?(message.symbol)
|
333
|
+
@listen[message.symbol].each { |listener| listener.callback.call(listener, message, *listener.args) }
|
334
|
+
end
|
335
|
+
}
|
336
|
+
rescue Terminate, Errno::EPIPE => error
|
337
|
+
raise error # on these errors we got to get out of the loop
|
338
|
+
rescue Exception => error
|
339
|
+
exception(error) # those errors are logged, reading goes on
|
340
|
+
end
|
341
|
+
|
342
|
+
def inspect # :nodoc:
|
343
|
+
"#<%s:0x%08x irc=%s>" % [
|
344
|
+
self.class,
|
345
|
+
object_id << 1,
|
346
|
+
@irc.inspect
|
347
|
+
]
|
348
|
+
end
|
349
|
+
end # Client
|
350
|
+
end # IRC
|
351
|
+
end # Butler
|
@@ -0,0 +1,53 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2007 by Stefan Rusterholz.
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE.txt for permissions.
|
5
|
+
#++
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
require 'butler/irc/users'
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
class Butler
|
14
|
+
module IRC
|
15
|
+
# Provides methods to see if hostmasks match
|
16
|
+
class Hostmask
|
17
|
+
Filter = [
|
18
|
+
[/([\\\[\]{}^`.-])/, '\\\\\1'],
|
19
|
+
[/\?/, "."],
|
20
|
+
[/\*/, ".*?"],
|
21
|
+
]
|
22
|
+
|
23
|
+
# FIXME, not yet correct, escaped * and ?
|
24
|
+
def initialize(mask)
|
25
|
+
@mask = mask.dup.freeze
|
26
|
+
filtered = Filter.inject('[]{}^`.-') { |s,(a,b)| s.gsub(a,b) }
|
27
|
+
#string.gsub(/[\\\x00-\x1f]/) { |match| ("\\%02x" % match[0]) } #implement later
|
28
|
+
@regex = Regexp.new("\A#{filtered}\z")
|
29
|
+
end
|
30
|
+
|
31
|
+
def matches?(mask)
|
32
|
+
mask = mask.hostmask if mask.kind_of?(User)
|
33
|
+
return !@regex.match(mask.to_str).nil?
|
34
|
+
end
|
35
|
+
|
36
|
+
def match(mask)
|
37
|
+
mask = mask.hostmask if mask.kind_of?(User)
|
38
|
+
@regex.match(mask.to_str)
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_str
|
42
|
+
@mask
|
43
|
+
end
|
44
|
+
alias to_s to_str
|
45
|
+
end
|
46
|
+
|
47
|
+
class User
|
48
|
+
def hostmask
|
49
|
+
return Hostmask.new("#{@nick||'*'}!#{@user||'*'}@#{@host||'*'}")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|