butler 1.8.0
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/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
|